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ô thức ảnh hưởng đến hệ thống phần mềm, đội ngũ và việc ra quyết định vào một nơi, bao quát nhiều lĩnh vực từ vận hành 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 ngũ như định luật Conway, định luật Brooks, số Dunbar cho thấy cấu trúc tổ chức ảnh hưởng trực tiếp đến thiết kế hệ thống và năng suất
- Ở mảng kiến trúc, các ràng buộc và nguyên tắc cần 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 tổng hợp lại
- Các quy luật liên quan đến chất lượng đề cập đến những khó khăn thực tế trong việc duy trì chất lượng mã và gỡ lỗi như nợ kỹ thuật, tháp kiểm thử, định luật Kernighan
- Ở 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 dễ gặp trong quá trình phát triển như hiệu ứng Dunning-Kruger, ngụy biện chi phí chìm, nguyên tắc Pareto
Đội ngũ (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 nguyên trạng cấu trúc giao tiếp của chính 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 nhóm được chia thành 3 phần thì hệ thống cũng có xu hướng được chia thành 3 mô-đun lớn
- Cũng có cách áp dụng ngược lại gọi là "chiến lược Conway ngược" (Inverse Conway Maneuver): 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 làm cho ranh giới nhóm và ranh giới dịch vụ trùng khớp là hiệu quả
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ẽ khiến nó còn chậm hơn
- Khi nhân sự mới tham gia, các thành viên hiện có phải tiêu tốn thời gian cho đào tạo và điều phối, khiến năng suất tổng thể giảm tạm thời
- Khi số thành viên tăng lên, số đường giao tiếp tăng theo cấp số nhân (với n người thì có n(n-1)/2 đường)
- 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 nhân lực, WIP (công việc đang làm) tăng nhưng throughput trì trệ, khiến lead time còn tăng lên
- Giải pháp không phải là bổ sung nhân lực 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
- Robin Dunbar rút ra con số này từ mối tương quan giữa kích thước não của loài 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 hệ tầng và quy trình chính thức
- "Nhóm hai pizza" 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 ở các đơ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 số thành viên trong nhóm tăng lê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 biếng xã hội" (social loafing)
- Trong các nhóm phần mềm, khi quy mô nhóm 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 nhóm nhỏ 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 sẽ đảm nhiệm một nửa tổng công việc
- Tổ chức càng lớn thì mức độ phụ thuộc vào một nhóm nhỏ người có hiệu suất cao càng sâu sắc
- Giải thích vì sao khi mở rộng nhóm, 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 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 được khoảng cách này và chuẩn bị cơ chế bù đắp
7. Nguyên lý Peter (Peter Principle)
Trong tổ chức, mọi nhân viên đều có xu hướng được thăng tiến đến mức độ bất tài của chính mình
- Mô tả mô thức một người giỏi ở một vai trò được thăng chức rồi lại trở nên kém năng lực ở vai trò mới
- Phản ánh thực tế rằng một lập trình viên xuất sắc chưa chắc sẽ trở thành quản lý giỏi
- Cho thấy sự cần thiết của hệ thống 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 thành viên tối thiểu 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ì tồn tại điểm lỗi đơn (Single Point of Failure)
- Điều quan trọng là nâng Bus Factor thông qua chia sẻ tri thức, pair programming và tài liệu hóa
- 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ế tác hại
- Một quan sát châm biếm do Scott Adams đưa ra, là biến thể của nguyên lý Peter
- Hiện tượng tổ chức mang tính nghịch lý trong đó 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à cội nguồn 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, có thể bỏ qua những cải thiện hiệu năng nhỏ"
- Vì khoảng 20% mã chiếm 80% thời gian chạy, nên tối ưu phần 80% mã còn lại là lãng phí
- Trình tự đúng là: trước tiên làm cho nó chạy được → làm cho nó đúng → nếu cần thì làm cho nó nhanh
- Vì mã đã tối ưu thường phức tạp hơn, chỉ nên thực hiện sau khi dùng profiling để xác định nút thắt cổ chai thực sự
11. Định luật Parkinson (Parkinson's Law)
Công việc sẽ giã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 việc đặt ra các cột mốc ngắn và rõ ràng rất quan trọng trong dự án phần mềm
- 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 luật 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% cuối cùng 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 (các edge case, hoàn thiện chi tiết, sửa lỗi) mất nhiều thời gian hơn dự kiến rất nhiều
- Cụm từ "gần như hoàn thành" trên thực tế có thể mới chỉ là điểm giữa 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 việc 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ự tham chiếu đệ quy, diễn tả bản chất khó khăn cố hữu của việc ước tính lịch trình phần mềm
- Thực tế là dù có thêm buffer, tiến độ vẫn tiếp tục bị vượt
- 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ó không còn là chỉ số đo lường tốt nữa
- Ví dụ điển hình là khi đặt code coverage làm KPI thì sẽ sinh ra hàng loạt bài kiểm thử vô nghĩa
- Nếu đo năng suất bằng số dòng mã (LOC), mã dài dòng không cần thiết sẽ được tạo ra
- Cần tập trung vào đạt được giá trị cốt lõi thay vì tối ưu chỉ số
15. Định luật Gilb (Gilb's Law)
Khi cần định lượng, đo lường theo bất kỳ cách nào vẫn tốt hơn là không đo lường
- Dù không thể đo lường một cách hoàn hảo, đo lường gần đúng vẫn luôn hữu ích hơn không đo
- Có thể áp dụng cho cả 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)
Nếu 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 phi chính thức như thời điểm thực thi, đị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 từng duy trì hành vi của phiên bản cũ để tương thích với ứng dụng bên thứ ba vốn phụ thuộc vào hành vi và lỗi không được tài liệu hóa trong quá khứ
- Được Hyrum Wright của Google quan sát trong quá trình thay đổi thư viện nội bộ Google vào khoảng năm 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 API chính thức mà là toàn bộ hành vi thực sự được quan sát
17. Định luật Gall (Gall's Law)
> Một hệ thống phức tạp hoạt động được tất yếu 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ế 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 khởi đầu từ một hệ thống hồ sơ đơn giản dành cho sinh viên Harvard năm 2004 rồi mở rộng dần
- Ngay cả khi chuyển sang microservices, việc bắt đầu từ monolith rồi tách dần từng bước vẫn có lợi hơn
- Do 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 bị rò rỉ (The Law of Leaky Abstractions)
> Mọi trừu tượng không tầm thường (non-trivial) đều sẽ 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 truy vấn đượ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 bên trong như GC pause vẫn ảnh hưởng đến hiệu năng
- Bài học không phải là trừu tượng là xấu, mà là phải chuẩn bị cho lúc trừu tượng bị vỡ
- Joel Spolsky giới thiệu điều này trong một bài blog năm 2002 cùng các ví dụ như TCP, bộ nhớ ảo
- Cũng 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 này (người dùng hay hệ thống)
- Calendly hấp thụ độ phức tạp của việc điều phối lịch hẹn vào hệ thống, trong khi chuỗi email lại đẩy nó sang cho người dùng
- Thiết kế tốt là 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 trong thập niên 1980 khi làm việc với Apple Lisa và GUI giai đoạn đầu
20. Định lý CAP (CAP Theorem)
> Một hệ phân tán chỉ có thể đảm bảo hai trong ba yếu tố: tính nhất quán (C), tính sẵn sàng (A) và khả năng chịu phân vùng (P)
- Network partition 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à tính nhất quán vs tính sẵn sàng
- Hệ CP (ví dụ: MongoDB): khi xảy ra phân vùng sẽ chặn ghi để duy trì đồng bộ giữa mọi replica
- Hệ AP (ví dụ: Cassandra, DNS): vẫn duy trì phản hồi yêu cầu trong lúc phân vùng, chấp nhận trạng thái không nhất quán tạm thời giữa các replica
- Eric Brewer đề xuất vào năm 2000 trong bối cảnh dịch vụ web, 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 có xu hướng xuất hiện một hệ thống kế nhiệm cồng kềnh và được thiết kế quá mức
- Từ thành công của hệ thống đầu tiên, người ta có thêm tự tin và đổ mọi ý tưởng vào hệ thống thứ hai
- Nguyên nhân chính là feature creep và over-generalization quá mức
- Được Frederick Brooks xác định trong The Mythical Man-Month
22. Các ngụy biện của điện toán phân tán (Fallacies of Distributed Computing)
> 8 giả định sai lầm mà người mới thiết kế hệ phân tán thường mắc phải
- 8 ngụy biện gồm: (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ố và vấn đề hiệu năng ngoài dự kiến
23. Định luật về hệ quả không chủ đích (Law of Unintended Consequences)
> Khi thay đổi một hệ thống phức tạp, cần dự liệu các kết quả ngoài dự kiến
- Khi thay đổi một thành phần của hệ thống, tác dụng phụ có thể xuất hiện ở những nơi không thể lường trước
- Là nguyên tắc 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, nó sẽ liên tục muốn thêm nhiều chức năng hơn
- Do Jamie Zawinski (nhà phát triển giai đoạn đầu của Netscape) quan sát
- Là 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 nền tảng vạn năng
Chất lượng (Quality)
25. Quy tắc Hướng đạo sinh (The Boy Scout Rule)
> Hãy để lại mã nguồn ở trạng thái tốt hơn lúc bạn tìm thấy nó
- Cốt lõi không phải là refactor quy mô lớn mà là cải thiện liên tục và từng bước
- Mỗi lần đều thực hiện một cải thiện nhỏ như sửa tên hàm gây khó hiểu, loại bỏ mã trùng lặp, thêm các bài 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 mã thì cũng nhận luô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ể xảy ra sai thì sớm muộn cũng sẽ xảy ra sai
- Là cơ sở cho defensive programming, xử lý ngoại lệ và thiết kế chịu lỗi
- Trong phần mềm, cần thiết kế xử lý lỗi và cơ chế fallback với tinh thần 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 nghiêm ngặt với những gì mình tạo ra, và rộng lượng với những gì mình nhận vào
- 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 chấp nhận nhiều định dạng khác nhau
- Là Robustness Principle do Jon Postel thiết lập khi thiết kế giao thức TCP/IP
- Hướng dẫn thực tiễn giúp tăng khả năng tương tác giữa các hệ thống
28. Thuyết cửa sổ vỡ (Broken Windows Theory)
> Đừng để mặc thiết kế tồi, quyết định sai và mã nguồn chất lượng thấp
- Chỉ một "cửa sổ vỡ" (một đoạn mã tệ) bị bỏ mặc cũng có thể kéo theo sự suy giảm chất lượng tiếp theo
- Khi codebase chất đống comment TODO, mã chết và cảnh báo chưa xử lý, mã mới cũng có xu hướng được viết ở mức chất lượng thấp hơn
- 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 lối tắt trong code là vay thời gian từ tương lai
- Gốc nợ (chi phí sửa) + lãi (sự suy giảm năng suất liên tục do mã 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 kiểm thử tự động: 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)
> Với đủ nhiều người xem xét, mọi lỗi đều dễ 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 xem xét mã 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)
> Debug khó gấp đôi so với lúc viết code ngay từ đầu
- Vì vậy, nếu bạn viết mã càng quá khéo léo, thì khi gỡ lỗi bạn sẽ càng không đủ khéo léo để xử lý nó
- Đây là lý do cần viết mã đơn giản, dễ đọc
- Được Brian Kernighan nêu 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ị chạy 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, nên đượ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 test case 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-type (phản ánh thế giới thực) nếu muốn tiếp tục được sử dụng thì việc thay đổi liên tục là không thể tránh khỏi
- Mỗi lần thay đổi, độ phức tạp đều tăng lên; nếu không chủ động quản lý, chất lượng sẽ suy giảm
35. Định luật Sturgeon (Sturgeon's Law)
> 90% mọi thứ đều vô dụng
- Được Theodore Sturgeon đưa ra để đáp lại những chỉ trích đối với văn học SF
- Cũng áp dụng cho phần mềm: trong phần lớn mã nguồn, công cụ và framework, thứ thực sự xuất sắc chỉ chiếm thiểu số
- Cần giữ tiêu chuẩn chất lượng cao và tập trung vào 10% thực sự 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ù bổ sung bao nhiêu bộ xử lý đi nữa, mức tăng tốc tối đa về mặt lý thuyết cũng chỉ là 20 lần
- Nhận thức được giới hạn của song song hóa và giảm điểm nghẽn tuần tự sẽ hiệu quả hơn
- Do Gene Amdahl đề xuất vào năm 1967
37. Định luật Gustafson (Gustafson's Law)
> Bằng cách tăng kích thước bài toán, có thể đạt được mức tăng tốc đáng kể trong xử lý song song
- 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ì cố định, việc thêm bộ xử lý sẽ phát huy hiệu quả
- Trong xử lý dữ liệu lớn, mô phỏng khoa học, v.v., 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 cho hiệu ứng mạng trong mạng xã hội, ứng dụng nhắn tin, marketplace, v.v.
- Đượ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 diễn duy nhất, rõ ràng và có thẩm quyền
- Không chỉ bao gồm sự trùng lặp mã nguồn mà còn cả sự trùng lặp của tri thức, logic và dữ liệu
- Trùng lặp khiến khi thay đổi phải sửa ở nhiều nơi cùng lúc, nên là nguyên nhân gây lỗi và bất nhất
- Được Andy Hunt và Dave Thomas xác lập 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í cho việc 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
- Có nguồn gốc từ nguyên tắc thiết kế do Hải quân Mỹ đưa ra vào thập niên 1960
41. Các 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ở với mở rộng, đóng với sửa đổi
- 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à nó không sử 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 SOLID
42. Định luật Demeter (Law of Demeter)
> Đối tượng chỉ nên tương tác với những “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ạ
- Là nguyên tắc nên tránh gọi chuỗi như
a.getB().getC().doSomething() - Giảm độ kết dính, tăng cường đóng gói và thu hẹp phạm vi ảnh hưởng khi 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 lập trình viên khác
- Hàm, API, UI nên có hành vi có thể dự đoán được qua tên gọi và convention
- Nếu hàm
delete()thực chất chỉ lưu trữ thì sẽ gây bất ngờ → lỗi thiết kế - Hành vi thiếu trực quan dẫn đến bug và sai sót củ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”, bạn sẽ tạo ra thiết kế thừa và gánh nặng bảo trì
- Để thực hành YAGNI cần có sự tự tin vào việc refactoring (test coverage 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 v.v. hãy thêm khi có yêu cầu
Ra quyết định (Decisions)
45. Hiệu ứng Dunning-Kruger (Dunning-Kruger Effect)
> Càng biết ít về một điều gì đó, con người càng có xu hướng tự tin hơ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ề hiểu biết của mình
- Điều quan trọng là nâng cao độ chính xác của tự nhận thức thông qua code review, mentoring và học tập liên tục
46. Dao cạo Hanlon (Hanlon's Razor)
> Đừng quy cho ác ý điều có thể được giải thích đầy đủ bằng sự ngốc nghếch hoặc bất cẩn
- Trước khi diễn giải mã kém chất lượng hoặc quyết định sai lầm của đồng nghiệp là hành vi phá hoại có chủ đích, hãy ưu tiên xem xét thiếu hiểu biết, sai sót, thiếu thời gian
- Là nền tảng của niềm tin và giao tiếp mang tính xây dựng trong nhóm
47. Dao cạo Occam (Occam's Razor)
> Cách giải thích đơn giản nhất thường là cách đúng nhất
- Khi gỡ lỗi, hãy kiểm tra khả năng đơn giản nhất trước khi nghĩ đến nguyên nhân phức tạp
- Trong thiết kế kiến trúc cũng nên ưu tiên tìm giải pháp đơn giản trước khi thêm các lớp trừu tượng 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 thiệt hại chỉ vì đã đầu tư thời gian hoặc công sức vào đó
- Tâm lý không thể từ bỏ vì đã bỏ ra quá nhiều thời gian dù tính năng phát triển suốt 6 tháng hóa ra đi sai hướng
- 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, v.v. chỉ là xấp xỉ của thực tại
- Không nên mù quáng tin vào 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 thu thập 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 là chìa khóa 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)
Có xu hướng đánh giá quá cao tác động ngắn hạn của công nghệ và đánh giá 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 kỳ vọng thổi phồng → vực thẳm vỡ mộng → sườn dốc khai sáng → trạng thái ổn định năng suất
- Khi áp dụng công nghệ mới (blockchain, AI, v.v.), không nên bị cuốn theo cơn sốt ngắn hạn mà cần đánh giá tính thực tiễn dài hạn
52. Hiệu ứng Lindy (The Lindy Effect)
Thứ gì đã được sử dụng lâu dài 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 nhiều khả năng sẽ còn tồn tại lâu dài
- Là 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 trong Antifragile
53. Tư duy nguyên lý gốc (First Principles Thinking)
Cách tư duy phân rã vấn đề phức tạp thành những 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 kiểu nghĩ “xưa nay vẫn luô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 rồi suy luận ngược từ đó
- Thay vì nghĩ “làm sao để thành công”, hãy nghĩ trước “làm sao để thất bại” để xác định yếu tố rủi ro
- Là nền tảng lý thuyết cho phân tích chế độ hỏng hóc (Failure Mode Analysis) và pre-mortem
- Một mô hình tư duy mà Charlie Munger thường xuyên sử dụng
55. Nguyên lý Pareto / quy tắc 80/20 (Pareto Principle)
80% vấn đề phát sinh từ 20% nguyên nhân
- Có xu hướng 80% lỗi nằm tập trung trong 20% mã nguồn
- Tập trung nguồn lực vào 20% có tác độ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 trong 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 chính xác 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 tích cực tham gia sửa thông tin sai hơn là trả lời câu hỏi
- Mang tên Ward Cunningham (người phát minh Wiki), nhưng thực ra tên gọi này do Steven McGeady đặt
- Là một góc nhìn có thể áp dụng cho tài liệu hóa và chia sẻ tri thức trong cộng đồng mã nguồn mở
1 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