3 điểm bởi GN⁺ 5 giờ trước | 1 bình luận | Chia sẻ qua WhatsApp
  • YAGNI không phải là một quy tắc tiết kiệm đơn giản kiểu “đừng viết mã chưa cần đến”, mà là một nguyên tắc nói về chi phí của việc đón đầu cấu trúc bằng suy đoán trước khi nhu cầu được xác nhận
  • Trọng tâm của vấn đề không phải là bản thân thiết kế, mà là thiết kế vào lúc nào; cấu trúc hóa quá sớm có thể nguy hiểm không kém cấu trúc hóa quá muộn
  • Cấu trúc đi trước tạo ra đồng thời chi phí quyền chọn khi khép lại các lựa chọn trước khi có thông tin, và chi phí NPV khi đẩy chi phí lên sớm còn lợi nhuận thì đến muộn
  • Ngay cả khi chi phí sinh mã gần như về 0, YAGNI vẫn không biến mất; ngược lại, việc tạo mã rẻ có thể khiến các framework dựa trên suy đoán dễ được dựng lên hơn
  • Kết luận “hãy làm khi cần” không phải vì chi phí viết mã, mà vì giá trị của quyền chọn chưa dùng và tiền chưa chi vẫn còn nguyên

YAGNI không cấm thiết kế

  • YAGNI là viết tắt của “You Aren’t Gonna Need It”, không phải cái cớ để nói rằng tuyệt đối đừng thiết kế thứ chưa cần
  • Nếu có thứ cần thiết thì cứ làm, nhưng điểm cốt lõi là thời điểm
  • Câu chuyện khởi nguồn từ một tình huống trong dự án: “3 tuần nữa cách triển khai đơn giản sẽ không đủ, nên tôi muốn làm thứ phức tạp hơn ngay bây giờ”, và câu đáp “You aren’t going to need it” cứ lặp đi lặp lại
  • Nguyên tắc này xem cả việc dựng cấu trúc quá sớm lẫn quá muộn đều là rủi ro

Vấn đề không phải chi phí viết mã mà là cấu trúc dựa trên suy đoán

  • Cách hiểu phổ biến xem YAGNI như một quy tắc tiết kiệm: “mã chưa cần đến thì đắt, nên đừng viết”
  • Nhưng thứ YAGNI nhắm đến không phải chi phí sản xuất mã, mà là cấu trúc suy đoán (speculative structure) được dựng sẵn trước khi tính năng thực sự cần đến
  • Kiểu cấu trúc này tạo ra hai loại chi phí ở những thời điểm khác nhau và vì những lý do khác nhau

Chi phí thứ nhất: quyền chọn

  • Nếu dựng cấu trúc trước khi tính năng xuất hiện, ta sẽ cam kết bằng suy đoán với những yêu cầu còn chưa biết
  • Tính năng được chuẩn bị từ trước thường khác với tính năng thực sự đến sau, và kết quả là phải trả chi phí hai lần
    • Chi phí lách qua một cấu trúc có hình dạng sai
    • Chi phí tháo dỡ và làm lại cấu trúc đó
  • Vấn đề này không chỉ dừng ở ý “dự đoán là khó”
  • Ngay cả khi suy đoán đúng, vẫn còn thiệt hại ở chỗ mất đi quyền chọn là không cam kết sớm và đợi đến sau để dựng cấu trúc đúng
  • Chờ đợi không phải lười biếng, mà là nắm giữ một tài sản mang tên quyền chọn

Chi phí thứ hai: NPV

  • Cũng như tiền có giá trị theo thời gian, tính năng cũng có giá trị theo thời gian
  • Nếu xây cấu trúc ngay bây giờ cho một tính năng phải 3 tháng nữa mới cần, thì chi phí bị kéo lên sớm còn việc phát hành tính năng thật sự tạo ra doanh thu lại bị lùi lại
  • Chi phí này vẫn phát sinh ngay cả khi suy đoán là đúng
  • Dự đoán hoàn hảo cũng không thể đổi thứ tự giữa chi phí và lợi nhuận; tổn thất xuất hiện trong khoảng chênh khi chi phí được đặt trước lợi nhuận
  • Chi phí quyền chọn là vấn đề “đừng cam kết trước khi có thông tin”, còn chi phí NPV là vấn đề “đừng trả tiền trước khi cần”
  • Ngay cả phản biện kiểu “để sau sửa thì quá đắt” cũng có thể là một dự đoán khác về chính việc cải tạo tốn kém đó

Dù sinh mã rẻ hơn, YAGNI vẫn còn đó

  • Trong cả hai loại chi phí này đều không có chi phí gõ mã
  • Khi chi phí viết mã gần như về 0, cách diễn giải YAGNI theo kiểu tiết kiệm rằng “mã đã rẻ hơn rồi thì làm trước cũng được” sẽ sụp đổ
  • Nhưng vì YAGNI không phải quy tắc tiết kiệm, việc tạo mã rẻ không làm YAGNI mất giá trị
  • Chi phí quyền chọn không sinh ra từ lượng công sức, mà từ cam kết khép lại các lựa chọn trong tương lai
  • Chi phí NPV không đến từ giá sản xuất, mà từ thời điểm của dòng tiền
  • Việc tạo mã miễn phí không làm YAGNI yếu đi, mà còn có thể khiến các framework dựa trên suy đoán dễ được dựng lên hơn
  • Cấu trúc được tạo ra vẫn tiếp tục tạo ra hai loại chi phí đó, và vì không tự tay viết nên mức độ hiểu biết về nó thậm chí có thể còn thấp hơn
  • Kết luận không phải là “hãy đợi vì mã đắt”, mà là quyền chọn và tiền có giá trị hơn khi chưa bị dùng đến, nên hãy làm khi cần

1 bình luận

 
Các ý kiến trên Hacker News
  • Tôi cho rằng chi phí thay đổi cấu trúc cũng đã giảm
    Nhờ AI, chi phí bổ sung kiểm thử cho hành vi trước khi thay đổi cấu trúc đã giảm, và chi phí triển khai migration không gián đoạn cũng đi xuống
    Một trong những lý do lớn khiến Rust được chú ý, ngay cả trước thời AI, là chi phí thay đổi cấu trúc nội bộ ứng dụng thấp; giờ điều đó còn đúng hơn
    Chi phí cơ hội của việc không thể thay đổi cấu trúc một cách an toàn đã tăng mạnh, và hiện nay tôi tối ưu hóa ưu tiên hàng đầu cho khả năng thay đổi nhanh chóng và an toàn những phần lớn của mã và sản phẩm

    • Khả năng thay đổi nhanh chóng và an toàn những phần lớn của mã và sản phẩm vốn đã là điều tốt, và có giá trị bất kể sự xuất hiện của lập trình bằng AI
      Tuy nhiên, trước AI, việc thay đổi cấu trúc mất nhiều thời gian hơn rất nhiều, nên cũng có thể nói giá trị của thứ mà ta đang tối ưu hóa hiện nay thực ra đã giảm
      Nó vẫn có giá trị, nhưng có thể ít hơn một chút so với trước đây
    • Khó đồng ý
      Khi các bài kiểm thử do AI tạo ra dễ vỡ ngày càng nhiều, chi phí thay đổi cấu trúc còn lớn hơn trước
      Việc dọn dẹp bộ kiểm thử để kiểm chứng bản chất của vấn đề, thay vì kiểm chứng các quyết định thiết kế ngẫu nhiên, vẫn là điều AI chưa làm tốt
    • Đúng vậy
      Nhưng nếu không làm như thế, thì cũng quá dễ để nhận được một bộ kiểm thử mong manh mới chín khoảng 75%
      Nhiều người hài lòng vì xem việc đi từ “một vài bài kiểm thử bình thường và mong manh do con người viết” sang “nhiều bài kiểm thử bình thường và mong manh do AI viết” là một cải thiện khách quan
      Tôi hoàn toàn đồng ý với việc tận dụng công cụ theo cách này, nhưng vì thế mà nói rằng ta không cần lo việc dựng một lâu đài trên mây sai lầm quá sớm thì khó chấp nhận
      Một hợp đồng kiểm thử hoàn hảo chịu được refactoring vẫn khá khó thiết kế
    • Điều này làm tôi nhớ đến nguyên tắc mở-đóng: “mở cho mở rộng, đóng cho sửa đổi”
      Cái cũ lại quay về như mới
      Từ hiệu quả ngữ cảnh của các cách tiếp cận như DDD hay kiến trúc sạch cho đến những mục này, AI không tạo ra một sự đánh đổi mới mà hoạt động như bộ khuếch đại
      Nó tăng năng suất của những đội làm đúng, và cũng làm tăng nợ của những đội có tiêu chuẩn thấp về chất lượng thiết kế và kiến trúc
    • Có vẻ như mọi người ngày càng tăng thêm những thao tác mò mẫm vô ích, đặt cược rằng AI sẽ sửa thay
      Thứ nhận được chỉ là không cần suy nghĩ sâu
      Mà suy nghĩ sâu cũng không tốn quá nhiều thời gian hay công sức, nên họ sẽ bị vượt qua bởi những người cũng dùng AI như vậy nhưng vẫn suy nghĩ đủ để không biến thành mò mẫm vô ích
  • Kent Beck ví đoạn mã chưa viết với một quyền chọn tài chính cho phép mua ở một mức giá nhất định
    Nhưng rốt cuộc đó chỉ là ẩn dụ, kéo đi quá xa thì sẽ trở nên kỳ quặc
    Nếu chưa viết dòng mã nào, liệu các lựa chọn có vô hạn không? Dù chưa tốn thời gian, điều đó có vẻ không đúng
    Nó cũng có thể trở thành lý do để ở mãi giai đoạn lập kế hoạch và trì hoãn việc viết mã vô thời hạn nhằm không chốt bất cứ điều gì
    Tuy vậy, nếu ẩn dụ này còn dùng được, thì chi phí có thể nằm ở việc đọc mã
    Mã chưa được viết thì không cần đọc, và nếu dùng coding agent thì nó cũng không làm rối ngữ cảnh bằng các chi tiết không liên quan
    Mã chưa viết cũng không cần kiểm thử, và các bài kiểm thử chưa viết cũng không tốn thời gian chạy
    Vì vậy nên giữ dự án nhỏ nhất có thể; trì hoãn tính năng sẽ làm chậm tối đa sự tăng trưởng của codebase
    Điều này cũng có nghĩa là chạy mã của người khác có thể tránh được nhiều chi phí
    Nếu có thể dùng API chuẩn thì không cần hiểu chi tiết phần triển khai hay chạy các bài kiểm thử của nó, nhưng thêm dependency cũng có rủi ro

    • Trong “Tidy, First?” của Kent, phần mềm tạo ra giá trị theo hai cách: việc làm hôm nay, và khả năng khiến việc mới ngày mai trở nên khả thi
      Mã chưa viết không có giá trị
      Để mã được viết hôm nay tạo ra giá trị, nó phải giải quyết yêu cầu hay issue của hôm nay, hoặc nghiêng về việc làm cho điều gì đó ngày mai dễ hơn
      Nếu dùng giải pháp chắp vá để tạo nợ kỹ thuật, hoặc lãng phí thời gian vào thứ trái với YAGNI, thì không tạo ra giá trị
      Điều quan trọng không phải là mã chưa viết, mà là mã sẽ viết trong tương lai và mục đích của nó
      Cần có sự đánh đổi đúng giữa việc giải quyết ticket/việc cần làm hôm nay và không tự bắn vào chân mình trong tương lai
      Viết mã là một cam kết; giá trị hôm nay thì thấy được, còn giá trị ngày mai gần với ước tính hơn
      Dù vậy, luôn có chi phí phải trả về sau, nên ta ước tính để cố dự đoán điều gì sẽ cần thiết và giảm thiểu chi phí
  • Tôi nghĩ các thực hành tốt nhất trong thời AI chưa được nhấn mạnh đủ, nhưng có một điểm Kent hoàn toàn bỏ sót
    Có giá trị đáng kể trong việc tìm ra nhanh hơn chức năng cần thiết là gì
    Việc tạo ra cấu trúc mang tính phỏng đoán có thể là cơ chế ép buộc giúp làm rõ yêu cầu, hoặc ít nhất bắt đầu phơi bày các dạng thất bại
    Nó có thể đắt hơn việc chờ đợi, nên không nên làm vậy với phần lớn yêu cầu, nhưng đôi khi đó có thể là lựa chọn tốt nhất
    Chi phí tạo ra thứ sai lầm nay đã thấp hơn nhiều, vì vậy phép tính xoay quanh YAGNI cũng thay đổi
    Tuy nhiên vẫn cần tính toán, và hiện mỗi đội phải tự tìm hiểu điều đó đã thay đổi với mình như thế nào

    • Nếu tạo cấu trúc phỏng đoán dưới dạng spike rồi vứt bỏ thì ổn
      Nếu không vứt bỏ, nó sẽ trở thành cơ chế ép buộc tạo ra một kết quả bừa bộn
    • Tôi cho rằng chức năng cần thiết vốn đã xuất phát từ yêu cầu và thiết kế hệ thống đáp ứng các yêu cầu đó
      YAGNI là vấn đề tạo ra thứ không có trong yêu cầu hiện tại vì dự đoán yêu cầu sẽ thay đổi sau này
      Nó khác với việc cụ thể hóa các yêu cầu và ràng buộc hiện tại
      Những thứ đó chủ yếu đến từ đối thoại với bên liên quan, người dùng, khách hàng, cũng như từ tài nguyên, ràng buộc kỹ thuật và năng lực
      Prototype có giá trị khi trao đổi với bên liên quan, xây dựng mô hình quản lý dự án hoặc làm nghiên cứu kỹ thuật
      Ngoài ra thì đó là đảo ngược trước sau
  • Cách nhìn phần mềm đang chạy như một tài sản là đúng
    Tuy nhiên chi phí chạy rồi làm lại đã giảm mạnh
    Chi phí chưa giảm là chi phí phá vỡ chuỗi tin cậy đối với kết quả có thể dự đoán
    Một phiên bản cụ thể của phần mềm đang chạy đã tích lũy niềm tin, và nếu viết lại từ đầu thì khi phát hành, phần vốn đó sẽ bị đặt lại về ban đầu

  • Đến một lúc nào đó tôi đổi cách nghĩ
    Trì hoãn những thứ cụ thể theo YAGNI, và viết một phiên bản trừu tượng nhất có thể
    Có nên tạo UserStore không? Trông có vẻ là đơn giản nhất, nhưng có thể không cần một dạng cụ thể tên là User
    Vì vậy tạo một Store chứa bất cứ thứ gì có thể lưu trữ
    Nếu chưa quen thì trông giống thiết kế quá mức và một đống generic, nhưng nghịch lý là đây là cách hứa hẹn ít nhất với bất kỳ triển khai cụ thể nào

    • Chính xác là trông như thiết kế quá mức và một đống generic
      Không chỉ tạo ra một interface UserStore có lẽ đã không cần, mà còn tạo luôn cả một abstraction Store tổng quát chắc chắn là không cần
      Tức là để tránh cam kết với triển khai cụ thể, lại đi triển khai những lớp dính nhớp rất có khả năng là không cần và sau này cũng không cần
      Nếu đó là abstraction không dựa trên nhu cầu thực tế, thì dù sau này có cần, khả năng cao là đã làm gần như sai
      Rốt cuộc UserStore sẽ cần, nên đáng lẽ phải tạo nó trước
  • Tôi không đồng ý với câu “đây không phải là lập luận rằng việc dự đoán là khó theo kiểu một kiến trúc sư sắc bén hơn thì có thể tránh được”
    Lập luận này chỉ đứng vững khi có tiền đề rằng dự đoán là khó

    • “Dù đoán đúng thì vẫn tệ hơn là không cam kết” cũng gây rối
      Nếu dựng sẵn nền cho một tính năng có xác suất rất cao và mọi thứ khớp nhau, bạn sẽ phát hành nhanh hơn
      Cũng không có gì đảm bảo đội nhất định sẽ lớn lên hoặc nhân sự sẽ được giữ nguyên, nên việc tự chúc mừng vì đã kiềm chế có vẻ tệ hơn là cuống cuồng cố làm đúng YAGNI ngay trước hạn chót
  • Gần đây tôi phải thoát về mặt chức năng khỏi một codebase chất đầy YAGNI, và dù có agent thì đó vẫn là khối lượng công việc khổng lồ
    Trong hệ thống phân tán, làm sao biết cái gì thật sự đang được dùng? Có thứ tôi bỏ sót, có thứ agent bỏ sót, và toàn bộ việc này mất nhiều thời gian hơn cần thiết
    Không chỉ đơn giản là port 1:1; vì tôi xem đây là cơ hội để đơn giản hóa, nên phải hiểu hoàn toàn hệ thống cũ hoạt động ra sao
    Ngay cả những thứ thực tế hoàn toàn không được dùng, nếu không nhận diện được như vậy thì vẫn bị đưa vào phạm vi cần hiểu

  • Cuối cùng, tôi nghĩ vấn đề quy về việc khám phá bài toán và triển khai lời giải
    Luôn có chi phí khi giải sai bài toán, và cũng có chi phí khi triển khai một lời giải tệ cho thứ không hề cần
    Phát triển phần mềm đôi khi có thể trôi thành thử-sai đơn thuần, thay vì suy nghĩ về chiến lược và tập hợp vấn đề cần khám phá
    Có những trường hợp việc khám phá vấn đề sâu hơn mức cần thiết theo một hướng cụ thể sẽ hữu ích về dài hạn, nhưng triển khai lời giải mà không có mục đích thì không bao giờ tốt
    Tôi nghĩ điều Kent Beck thật sự phê phán là thái độ triển khai thứ gì đó “phòng khi cần” chỉ vì có thể tương lai sẽ cần

  • Bài viết nói “nếu tạo cấu trúc trước khi tính năng đến, tức là cam kết với phỏng đoán”, nhưng tôi nghĩ đằng nào cũng đều có phỏng đoán
    Khả năng tính năng sẽ đến có thể cao, nhưng không chắc chắn
    Nếu bây giờ không tạo cấu trúc thì có chi phí refactor; nếu tạo quá sớm mà tính năng không đến thì lãng phí công sức
    Chi phí, xác suất và đánh đổi giữa các khả năng đó là gì? Đương nhiên tùy tình huống
    Toàn bộ YAGNI cố ý là một sự khái quát hóa lớn
    Rốt cuộc còn tùy hoàn cảnh
    Dù theo hướng nào, thường vẫn đầy phỏng đoán và lập luận kiểu vung tay mô tả, giống vấn đề ước lượng công việc đáng tin cậy
    Một số lập trình viên không chịu được thế giới bất định nên cố tìm quy tắc trắng-đen cho mọi thứ

  • Tôi chưa từng thấy trong bài của Kent Beck có nội dung nào hữu ích cho công ty chip
    Ở công ty chip, nhiều người phải làm việc trong thời gian dài, khách hàng không thể thấy bất cứ thứ gì cho đến khi hoàn thành, và muốn kiếm tiền thì phải bán theo đơn vị hàng triệu con

    • Chẳng phải ông ấy là người làm phần mềm và viết về phát triển phần mềm sao
      Phần cứng có những ràng buộc rất mạnh
      Ngay từ đầu, lý do phần mềm được phát minh cũng chính là để thoát khỏi những ràng buộc đó
    • Nói khách hàng tuyệt đối không thể thấy trước khi hoàn thành thì không hẳn đúng
      Đúng là nhiều công ty chip không chia sẻ công việc đang làm dở, nhưng việc chia sẻ mô phỏng, nguyên mẫu, mẫu kỹ thuật là có thể và thực tế cũng diễn ra
      Tất nhiên thường phải là khách hàng lớn
      Những hiểu biết từ ngành có chi phí thay đổi tương đối nhỏ không dễ áp dụng cho ngành có chi phí thay đổi lớn, và chiều ngược lại cũng thường như vậy
    • Thú vị thật
      Các công ty chip lên kế hoạch cho những dự án như vậy thế nào? Agile, waterfall, hay dùng một framework khác với ngành phần mềm?