2 điểm bởi GN⁺ 2024-08-07 | 1 bình luận | Chia sẻ qua WhatsApp
  • pv_controller.go của Kubernetes là controller đồng bộ binding PV/PVC; ngay từ đầu tệp đã nhấn mạnh “đừng đơn giản hóa, hãy giữ kiểu space shuttle style”
  • Kiểu này đặt else tương ứng cho mọi if và để lại chú thích ngay cả cho những điều kiện tưởng như hiển nhiên, nhằm thể hiện trong mã các nhánh đã được rà soát và ý định của chúng
  • Trọng tâm thiết kế là con trỏ hai chiều nối pvc.Spec.VolumeNamepv.Spec.ClaimRef, xử lý theo cách có thể phục hồi các tình huống cạnh tranh, xóa, chỉnh sửa từ người dùng và binding đồng thời trong môi trường không có transaction
  • Controller quản lý chuyển đổi trạng thái binding bằng cách kết hợp theo dõi thay đổi PV/PVC, cache nội bộ, hàng đợi một worker, ghi nhận event, dynamic provisioning và giao diện CSI migration
  • Các nhánh và chú thích dài dòng là cơ chế để bảo tồn tri thức nghiệp vụ về hành vi và bối cảnh khôi phục lỗi, nên các thay đổi về sau cũng phải theo cùng phong cách

Vai trò và nguyên tắc viết của pv_controller.go

  • pv_controller.go là tệp triển khai PersistentVolumeController trong package persistentvolume của Kubernetes
  • Controller này đồng bộ trạng thái của PersistentVolumeClaimPersistentVolume
    • Cache controller theo dõi thay đổi của PersistentVolume
    • Cache controller theo dõi thay đổi của PersistentVolumeClaim
    • Đồng bộ trạng thái PV/PVC dựa trên event thay đổi của hai đối tượng
  • Chú thích ở đầu tệp liên tục cảnh báo không được đơn giản hóa đoạn mã này
    • Tên phong cách là space shuttle style
    • Đây là cách đặt else tương ứng cho mọi câu lệnh if
    • Mục đích là biểu diễn rõ mọi nhánh, ngoại trừ kiểm tra lỗi đơn giản
    • Ngay cả những hành vi tưởng như hiển nhiên cũng được ghi thành chú thích để người bảo trì có thể lần theo độ phức tạp của binding

Vì sao cần giữ space shuttle style

  • Controller này là kết quả của việc hợp nhất công việc vốn từng được chia thành ba controller
  • Trong quá trình đơn giản hóa subsystem PV, cần một cách xử lý tường minh mọi điều kiện trong mã
  • Kết quả là mã có thể trông dài dòng, nhiều chú thích và nhiều nhánh
  • Sự dài dòng này là cơ chế để lưu lại trong mã tri thức nghiệp vụ và ngữ cảnh của hành vi binding
  • Khi sửa tệp này, cần bảo toàn space shuttle style và nếu cần thì thêm nhánh cùng chú thích theo cùng cách

Thiết kế cốt lõi: con trỏ hai chiều giữa PV và PVC

  • Trọng tâm thiết kế là con trỏ hai chiều giữa PV và PVC
    • Con trỏ phía PVC: pvc.Spec.VolumeName
    • Con trỏ phía PV: pv.Spec.ClaimRef
  • Tính hai chiều này khó xử lý trong hệ thống không có transaction, nhưng cần thiết để bảo đảm hoạt động đúng cả trong tình huống lỗi
  • Nếu một rogue HA controller instance tạo ra tình trạng cạnh tranh, có thể xuất hiện nhiều binding không thể phân biệt, dẫn đến nguy cơ mất dữ liệu
  • Controller về cơ bản được thiết kế để hoạt động ở chế độ high availability active-passive
    • Chuyển đổi đối tượng được thiết kế để vẫn có thể hoạt động trong HA active-active
    • Tuy nhiên, nếu hai controller active thường xuyên xung đột, hiệu năng có thể giảm

Cách binding và điều kiện phục hồi

  • Controller hỗ trợ các đối tượng pre-bound hai chiều
    • PVC muốn một PV cụ thể
    • PV được đặt trước cho một PVC cụ thể
  • Binding diễn ra qua hai bước
    • Trước tiên sửa PV.Spec.ClaimRef
    • Tiếp theo sửa PVC.Spec.VolumeName
  • Ở bất kỳ thời điểm nào trong quá trình này, PV hoặc PVC đều có thể bị người dùng hoặc controller khác sửa/xóa
  • Hai controller trở lên cũng có thể đồng thời cố binding các volume và claim khác nhau
  • Controller phải có khả năng phục hồi các tình huống xung đột như vậy

Các thành phần chính của struct controller

  • PersistentVolumeController có các lister, hàm đồng bộ informer, Kubernetes client, event recorder, trình quản lý volume plugin… cần thiết cho đồng bộ PV/PVC
  • Phiên bản PV/PVC được biết đến gần nhất được lưu trong cache nội bộ
    • volumes persistentVolumeOrderedIndex
    • claims cache.Store
  • Cache này phản ánh cả phiên bản mới nhất đã lưu vào API server lẫn phiên bản nhận được qua event từ etcd
  • Một binding có thể tạo ra khoảng bốn event
    • Cập nhật volume.Spec
    • Cập nhật volume.Status
    • Cập nhật claim.Spec
    • Cập nhật claim.Status
  • Nếu không có cache nội bộ, khi informer đang giữ trạng thái cũ, controller có thể cố sửa lại một binding đã hoàn tất
  • Khi đó, việc thử ghi lại vào API server có thể gây xung đột phiên bản với đối tượng đã được lưu

Workqueue và ràng buộc đồng thời

  • Controller có workqueue riêng để xử lý claim và volume
    • claimQueue
    • volumeQueue
  • Mỗi queue chỉ được có đúng một worker thread
  • Đặc biệt, syncClaim() không reentrant
  • Nếu hai syncClaim() chạy đồng thời, các vấn đề sau có thể xảy ra
    • Binding hai claim khác nhau vào cùng một volume
    • Binding một claim vào hai volume
  • Controller có thể phục hồi các tình huống này bằng lỗi version từ API server và kiểm tra riêng, nhưng cách multi-worker có thể làm giảm tốc độ tổng thể

syncClaim: điểm vào đồng bộ PVC

  • syncClaim là phương thức chính được gọi khi claim được tạo, cập nhật hoặc đồng bộ định kỳ
  • Phương thức này không phân biệt loại event
  • Trước tiên, nó đặt migration annotation đúng cho PVC và cập nhật lên API server nếu cần
  • Sau đó phân nhánh theo việc có hay không annotation AnnBindCompleted
    • Nếu không có annotation: syncUnboundClaim
    • Nếu có annotation: syncBoundClaim
  • Phần xử lý thực tế được tách thành các phương thức cho unbound claim và bound claim để dễ đọc

checkVolumeSatisfyClaim: kiểm tra yêu cầu PV

  • checkVolumeSatisfyClaim kiểm tra PV được yêu cầu có đáp ứng yêu cầu của PVC hay không
  • Các điều kiện kiểm tra được liệt kê tường minh trong mã
    • Lỗi nếu PV có DeletionTimestamp
    • Lỗi nếu dung lượng PV nhỏ hơn dung lượng PVC yêu cầu
    • Lỗi nếu storageClassName khác nhau
    • Nếu feature gate VolumeAttributesClass bật, kiểm tra VolumeAttributesClassName có khớp hay không
    • Nếu feature gate tắt nhưng claim hoặc volume có VolumeAttributesClassName, báo lỗi
    • Lỗi nếu volumeMode không tương thích
    • Lỗi nếu access mode không tương thích
  • Nếu vượt qua mọi điều kiện, trả về nil

Xử lý event cho PVC delayed binding

  • emitEventForUnboundDelayBindingClaim tạo event cung cấp thông tin cho claim chưa bind ở chế độ delayed binding
  • Reason mặc định là WaitForFirstConsumer
  • Message mặc định cho biết sẽ chờ binding cho đến khi consumer đầu tiên được tạo
  • Nếu có Pod chưa được schedule tham chiếu đến PVC đó, reason đổi thành WaitForPodScheduled
    • Nếu có nhiều Pod, message sẽ chứa tên của tất cả Pod
    • Trong volume scheduling chỉ xét một Pod, nhưng vì không biết Pod nào được dùng nên đưa vào tất cả Pod

syncUnboundClaim: xử lý PVC chưa được bind

  • Nếu claim.Spec.VolumeName trống, nghĩa là người dùng chưa yêu cầu PV cụ thể
  • Trong trường hợp này, controller kiểm tra chế độ delayed binding của claim và dùng findBestMatchForClaim để tìm PV phù hợp nhất
  • Nếu không có PV phù hợp, xử lý theo thứ tự sau
    • Nếu có thể gán StorageClass mặc định, cập nhật PVC và kết thúc đồng bộ
    • Nếu là delayed binding và chưa ở trạng thái provisioning, tạo event chờ
    • Nếu claim có StorageClass, thử dynamic provisioning bằng provisionClaim
    • Nếu không, ghi event FailedBinding rằng không có PV khả dụng và cũng không có StorageClass
  • Nếu có PV phù hợp, gọi bind để bind PV với PVC
    • Khi thành công, ghi metric cho tác vụ provision + binding và dọn cache timestamp
    • Nếu lỗi khi lưu, lần syncClaim sau sẽ hoàn tất binding

Xử lý PVC yêu cầu PV cụ thể

  • Nếu claim.Spec.VolumeName không trống, nghĩa là người dùng đã yêu cầu một PV cụ thể
  • Nếu PV được yêu cầu không có trong cache, cập nhật trạng thái PVC thành Pending và thử lại sau
  • Nếu PV được yêu cầu tồn tại và volume.Spec.ClaimRef không có, PV vẫn chưa được claim
    • Kiểm tra yêu cầu bằng checkVolumeSatisfyClaim
    • Nếu không đáp ứng yêu cầu, ghi event VolumeMismatch và giữ PVC ở Pending
    • Nếu đáp ứng, gọi bind
  • Nếu PV được yêu cầu đã được claim bởi chính PVC này, gọi bind để hoàn tất binding
  • Nếu PV được yêu cầu đã gắn với claim khác, xử lý như sau
    • Nếu claim không có annotation cho biết đã được controller bind, ghi event FailedBinding và để ở Pending
    • Nếu có vẻ là controller đã bind nhưng lại gắn với claim khác, trả về lỗi cho trạng thái “should never happen”

syncBoundClaim: xử lý PVC đã được bind

  • syncBoundClaim xử lý PVC có annotation AnnBindCompleted
  • Nếu claim đã được bind nhưng claim.Spec.VolumeName trống, đổi trạng thái claim thành ClaimLost
    • Event message cho biết bound claim đã mất tham chiếu PV và dữ liệu của volume đã mất
  • Nếu PV mà claim trỏ tới không tồn tại, cũng đổi sang ClaimLost
    • Event message cho biết bound claim đã mất PersistentVolume và dữ liệu đã mất
  • Nếu PV tồn tại nhưng volume.Spec.ClaimRef không có, xem như volume đã chuyển sang trạng thái unbound và gọi lại bind
  • Nếu ClaimRef.UID của PV giống UID của claim, xem là trạng thái binding bình thường và gọi bind
    • Trong hầu hết trường hợp, lời gọi này không làm gì
  • Nếu PV trỏ tới claimant khác, đặt phase của claim thành trạng thái terminal Lost

syncVolume: điểm vào đồng bộ PV

  • syncVolume là phương thức chính được gọi khi volume được tạo, cập nhật hoặc đồng bộ định kỳ
  • Không phân biệt loại event
  • Trước tiên, nó đặt migration annotation và finalizer đúng cho PV rồi cập nhật lên API server nếu cần
  • Nếu volume.Spec.ClaimRef không có, xem đây là volume chưa được dùng và đặt phase thành Available
  • Nếu có ClaimRef nhưng UID trống, xem đây là PV được đặt trước cho một PVC cụ thể và đặt phase thành Available
    • PVC đó chưa bind với PV này, và PVC sync sẽ xử lý

Xử lý PV không tìm thấy claim

  • Nếu PV đã bind với claim, controller tìm PVC theo namespace/name trong ClaimRef
  • Khi không tìm thấy PVC trong cache, nó thực hiện kiểm tra bổ sung trong một số điều kiện
    • Kiểm tra lại trong informer cache
    • Kiểm tra lại ở API server
  • Với PV do external PV provisioner hoặc external PV binder tạo, khi tải lớn PVC có thể chưa kịp đồng bộ vào cache cục bộ
  • Để tránh reclaim nhầm PVC, controller thực hiện kiểm tra kép
  • Nếu xác định claim không tồn tại, đổi phase của volume thành Released và chạy reclaimVolume
    • Nếu phase hiện tại là Failed thì không ghi đè
    • Nếu reclaim policy là Retain, ghi log rằng PV đang tham chiếu đến claim không tồn tại

Khi liên kết PV và PVC bị lệch

  • Nếu claim tồn tại nhưng claim.Spec.VolumeName trống, nghĩa là PVC chưa có tên PV
  • Nếu volumeMode không khớp, ghi event VolumeMismatch ở cả PV và PVC, rồi bỏ qua syncClaim
  • Nếu không mismatch, thêm claim vào claimQueue để syncClaim sớm được gọi
    • Cách này giúp binding của provisioned volume diễn ra nhanh hơn
  • Nếu Spec.VolumeName của claim bằng tên volume hiện tại, xem là binding bình thường và cập nhật phase của volume thành Bound
  • Nếu claim đã bind với volume khác, xử lý tùy tình huống
    • Nếu là volume được provision động và reclaim policy là Delete, đánh dấu Released và chạy reclaimVolume
    • Nếu là volume do controller bind, dọn dẹp bằng unbindVolume
    • Nếu là con trỏ do người dùng tạo, giữ nguyên nhưng gọi unbindVolume để cập nhật phase và xóa ClaimRef.UID

Cập nhật trạng thái và phát event

  • updateClaimStatus lưu status của PVC vào API server
    • Thay đổi phase
    • Khởi tạo AccessModes, Capacity, CurrentVolumeAttributesClassName khi không có volume
    • Cập nhật access mode, capacity, tên current volume attributes class khi có volume
  • Có điều kiện chỉ cập nhật capacity vào thời điểm claim chuyển thành Bound
    • Vì khác biệt giữa filesystem size của PVC và block device size của PV có thể là có chủ ý, nên không ghi đè capacity của claim đã bound
  • Nếu feature gate VolumeAttributesClass bật, CurrentVolumeAttributesClassName được đặt trong lúc chuyển từ pending sang bound
    • Sau đó resizer hoặc admin override phải xử lý; nếu controller tiếp tục đặt thì có thể xảy ra race condition
  • updateClaimStatusWithEventupdateVolumePhaseWithEvent chỉ phát event khi status/phase thực sự thay đổi

Gán StorageClass mặc định

  • assignDefaultStorageClass tìm và gán StorageClass mặc định khi claim chưa có storage class
  • Claim đã có storage class thì bỏ qua
  • Nếu không có class mặc định, không cập nhật và trả về false
  • Nếu có class mặc định, đặt tên class vào claim.Spec.StorageClassName và cập nhật lên API server

Phạm vi tệp và giới hạn tường minh

  • Theo metadata của tệp hiển thị trên trang GitHub, pv_controller.go2038 dòng, 1864 LOC, 91 KB
  • Nội dung được cung cấp chỉ bao gồm phần đầu tệp đến đoạn bắt đầu hàm bindVolumeToClaim; phần còn lại nối tiếp bằng link raw view
  • Vì vậy, bản tóm tắt này chỉ giới hạn trong cấu trúc controller, chú thích thiết kế, các nhánh đồng bộ chính và logic cập nhật trạng thái thể hiện trong phần mã được cung cấp

1 bình luận

 
GN⁺ 2024-08-07
Các ý kiến trên Hacker News
  • Không biết có lạ không khi code trong file này thật sự cho cảm giác như code Go bình thường. Vì là Go nên dài dòng, và vì không dựa vào các tầng trừu tượng sâu nên trông dài hơn, nhưng bản thân code thì có vẻ khá điển hình.
    Trừu tượng hóa là con dao hai lưỡi, nên cách này cũng ổn; nếu không có phần mở đầu, có lẽ tôi đã không nghĩ lại lần hai về phong cách viết này. Có lẽ khác biệt đến từ việc tôi có nhiều kinh nghiệm với phần mềm doanh nghiệp hơn là phần mềm hệ thống. Với người đóng góp thường xuyên cho Kubernetes, các chú thích này có thể trông thừa, nhưng trong môi trường doanh nghiệp, nếu đây là code mà độc giả ở tương lai xa sẽ đọc mà không có ngữ cảnh, thì với mức độ phức tạp này có lẽ tôi còn chú thích nhiều hơn.

    • Trước đây kiểu code này từng cho cảm giác bình thường, nhưng trong khoảng 10 năm trở lại đây, có vẻ nhiều người đánh giá sự ngắn gọn cao hơn sự tường minh.
      Đặc biệt với những đoạn code quan trọng như thế này, tôi thích sự tường minh hơn rất nhiều. Trong sự nghiệp, đã nhiều lần tôi gặp code gộp nhiều điều kiện và lược bỏ các chú thích giải thích ngữ cảnh nghiệp vụ cũng như ý nghĩa, khiến tôi không thể xác định hành vi hiện tại là chủ ý hay tình cờ. Cách này dễ trở thành code ngăn cản thay đổi, chứ không phải code bền vững trước thay đổi, và ít nhất khiến người không phải tác giả khó sửa. Tạo ra những hàng rào Chesterton không cần thiết là đi ngược lại khả năng bảo trì.

    • Chú thích này có lẽ được thêm vào sau khi ai đó cố đơn giản hóa code rồi thất bại, như một cảnh báo cho người bảo trì tương lai rằng hãy nghĩ lại trước khi thử làm điều tương tự.
      Commit thêm cảnh báo là "Add note about space-shuttle code style"[1], và commit ngay trước đó là "Revert controller/volume: simplify sync logic in syncUnboundClaim"[2].

      [1] https://github.com/kubernetes/kubernetes/commit/de4d193d45f6...

      [2] https://github.com/kubernetes/kubernetes/commit/8a1baa4d64ca...

    • Tôi cũng từng nghĩ tương tự, cho đến khi nhìn thấy các câu lệnh if lồng nhau rất sâu thì đổi ý. Đoạn đó chắc chắn tôi sẽ tạo các nhánh return sớm.
      Cảm giác như họ chỉ làm xong bước đầu trong "làm cho nó chạy, làm cho nó nhanh, làm cho nó đẹp", rồi không làm bước "làm cho nó đẹp". Khi gỡ những tương tác trạng thái khó nhằn, tôi từng viết kiểu code xấu xí và nhiều chú thích như vậy, nhưng thường sẽ dọn dẹp một chút trước khi review. Có lẽ tốt hơn là dán một banner lớn ở đầu file ghi "đừng cố đơn giản hóa code này". Dù vậy, rõ ràng nó cũng không quá tệ.

    • Có thể là lạ, nhưng bạn không đơn độc. Với tôi code này trông hoàn toàn bình thường. Tôi từng viết code và chú thích kiểu này cho các thành phần mà tôi cảm thấy quan trọng đối với độ tin cậy của hệ thống.
      Tôi chưa bao giờ đồng tình với trào lưu "code không chú thích", và khi quay lại sau vài tháng hay vài năm, những chú thích do chính tôi viết quá thường xuyên trở nên vô giá với tôi trong tương lai. Khó tưởng tượng việc lần lại logic được nhúng trong một thành phần có độ phức tạp như thế này mà không có chú thích vững chắc.

    • Đặc biệt, lời giải thích rằng mọi if đều có chú thích else tương ứng dường như không phải lúc nào cũng đúng. Nhiều if không có phần tương ứng chỉ là các kiểm tra đơn giản kiểu if (err != nil) { hoặc các trường hợp return sớm khác, nhưng ngay cả khi loại những cái đó ra thì có vẻ vẫn có các if không có phần tương ứng.
      Tuy nhiên, theo kinh nghiệm phần mềm doanh nghiệp của tôi, chú thích bổ sung cũng không hẳn là nhiều. Trong codebase, chú thích // end if lan như bệnh dịch, nhưng chú thích thật sự giải thích thì hiếm.

  • Bài viết về chất lượng phần mềm Space Shuttle: https://archive.is/HX7n4
    Trích ra thì, điều đáng kinh ngạc ở phần mềm này không phải là nó làm được bao nhiêu việc, mà là nó hoạt động tốt đến mức nào. Nó không bao giờ crash, không cần khởi động lại, không có bug, và gần như hoàn hảo ở mức con người đạt được. Ba phiên bản cuối cùng mỗi phiên bản có 420.000 dòng, nhưng mỗi phiên bản chỉ có một lỗi; tổng số lỗi của 11 phiên bản cuối cùng là 17. Người ta nói rằng một chương trình thương mại có độ phức tạp tương tự sẽ có khoảng 5.000 lỗi.

    • Tôi tò mò chính xác "ba phiên bản cuối cùng mỗi phiên bản có 420.000 dòng và mỗi phiên bản có một lỗi" nghĩa là gì. Nếu mỗi trong ba phiên bản có đúng một bug, chẳng phải đó là cách diễn đạt kỳ lạ cho việc hai lần sửa trước đó không hoạt động, hoặc đã đưa thêm bug mới vào sao?
    • Sẽ thú vị nếu so sánh xem cách của NASA và cách của SpaceX khác nhau thế nào. SpaceX cũng đã thực hiện nhiệm vụ có người lái, nên yêu cầu có vẻ khá tương tự.
    • 5000 / 17 ≈ 295. Có công bằng không nếu giả định rằng chương trình thương mại có độ phức tạp tương tự đã tốn ít hơn 295 lần số person-hour?
    • Vấn đề của phương pháp phát triển Space Shuttle là nó cực kỳ đắt đỏ và chậm, nhưng vẫn không 100% không có bug.
      Vì nó quá đắt và chậm, chứng minh tính đúng đắn của phần mềm bằng các proof assistant hiện đại có lẽ sẽ rẻ hơn và nhanh hơn nhiều, đồng thời thực sự an toàn hơn. Các dự án như seL4, CompCert cho thấy nên làm thế nào.
    • Đây là một trong những bài tôi thích nhất. Thật đáng kinh ngạc là một bài viết trên Internet từ năm 1996 vẫn còn truy cập được.
  • Tôi hiểu ý đồ của // KEEP THE SPACE SHUTTLE FLYING., nhưng cũng hơi buồn cười khi trong chú thích lại tham chiếu đến một hệ thống không còn được vận hành vì hồ sơ an toàn không tốt.
    Khoảng 10 năm nữa, liệu mọi người còn nhớ tốt về Space Shuttle không?

    • Các vấn đề an toàn của Space Shuttle phần lớn là vấn đề phần cứng, không phải vấn đề phần mềm.
      Trong "Appendix F - Personal Observations on Reliability of Shuttle" [0], phụ lục của Richard Feynman trong báo cáo tai nạn Challenger năm 1986, có viết như sau:

      Tóm lại, hệ thống kiểm tra phần mềm máy tính và thái độ đối với nó có chất lượng cao nhất. Không thấy quá trình tự lừa dối dần dần bằng cách hạ thấp tiêu chuẩn, vốn là đặc trưng trong các hệ thống an toàn của Solid Rocket Booster hay Space Shuttle Main Engine.

      Ông đặc biệt nhấn mạnh chất lượng phần mềm avionics như một ví dụ cho thấy ngay cả các dự án chính phủ lớn và phức tạp như Shuttle cũng có thể được kỹ nghệ đúng cách, và bản thân chúng không tất yếu là chất lượng thấp hay nguy hiểm.

0: https://www.nasa.gov/history/rogersrep/v2appf.htm

  • Đã đưa con người và thiết bị lên không gian rồi đưa họ trở về nhà trong hơn rất nhiều so với 100 nhiệm vụ thành công. Đến nay tôi vẫn nhìn nhận nó tích cực, và nhiều khả năng sau này cũng vậy. Xét về tiến bộ của nhân loại và hiệu quả ròng thì đó là một thành công

  • Thứ chấm dứt Shuttle không phải là hồ sơ an toàn tệ, mà là chi phí và dự báo về sự suy giảm an toàn trong tương lai
    Dù hai tai nạn Shuttle đã khiến nhiều phi hành gia thiệt mạng hơn bất kỳ thảm họa NASA nào khác, nhưng nếu xét đến độ khó của những việc thực sự đã diễn ra, hồ sơ an toàn đó thật sự đáng kinh ngạc. Mã trông rất tốt

  • Tình huống của Space Shuttle phức tạp hơn là chỉ nói an toàn kém. Nếu tính theo nhiệm vụ, hồ sơ của nó còn khá tốt so với các phương tiện phóng khác. Shuttle có 2 nhiệm vụ gây chết người trong 135 lần, còn Soyuz thời Liên Xô có 2 trong 66 lần, SpaceShipTwo thì có hồ sơ tệ đến đáng sợ: 1 nhiệm vụ gây chết người chỉ trong 12 chuyến bay
    Tuy nhiên, Space Shuttle có sức chứa phi hành đoàn lớn hơn nhiều so với nhu cầu của phần lớn nhiệm vụ. Khác với Apollo hay Soyuz với 3 người, nó có thể chở tối đa 8 người; và nếu nghĩ đến việc phần lớn nhiệm vụ của Liên Xô/Roscosmos, ESA, CNSA là nhiệm vụ hoàn toàn không người lái và tự động, thì ngay từ đầu đã không có phi hành đoàn nào bị đặt vào rủi ro. Có lẽ phép ví von này hợp với Kubernetes hơn. Đó là một hệ thống được kỹ thuật hóa cao, mạnh mẽ, đa dụng nhưng đòi hỏi nhiều sự chú ý, và có lẽ được dùng hơi nhiều hơn mức cần thiết

  • Nếu xét theo thước đo phổ biến nhất là trên mỗi hành khách-dặm, Space Shuttle thuộc nhóm những phương tiện an toàn nhất từng được chế tạo và bay
    Thành thật mà hỏi, với tư cách một người có tuổi thơ đúng vào thập niên 1980, tôi không hiểu làm sao có thể không nhớ về nó một cách tích cực. Có phải vì quá nhỏ nên bạn chỉ nhìn lại chương trình này cùng mọi nhiệm vụ và thành tựu của nó một cách hồi tưởng, và chỉ có góc nhìn bị nhuốm màu bởi không khí thời nay, khi các nhà thầu không gian tư nhân là trung tâm?

  • Câu chuyện Richard Hipp đưa mã SQLite phù hợp với tiêu chuẩn hàng không cũng khá thú vị: https://corecursive.com/066-sqlite-with-richard-hipp/#testin...

    DO-178B. Đây là tiêu chuẩn chất lượng cho các sản phẩm hàng không thiết yếu về an toàn... Kiểm thử phải khiến mỗi phép rẽ nhánh trong mã nhị phân kết quả được thực thi ít nhất một lần, và được đi qua ít nhất một lần... Việc đó mất một năm, mỗi tuần 60 giờ... Nó tạo ra khác biệt rất lớn. Trong 8–9 năm sau đó, về cơ bản không có lỗi

  • Phần này làm tôi nhớ đến kiểm tra tính đầy đủ trong mã TypeScript. Tôi luôn cố gắng dùng nó
    https://www.typescriptlang.org/docs/handbook/2/narrowing.htm...

    • satisfies never mới hơn rất phù hợp cho mục đích này. Nó cũng tiện khi theo sở thích mà dùng chuỗi if else

    • Bạn cũng có thể thích ts-pattern

      https://github.com/gvergnaud/ts-pattern

  • Chỉ xét các trường hợp gắn else tường minh cho mọi if không hoàn toàn tầm thường, tôi tự hỏi mã này sẽ đơn giản hơn đến mức nào nếu những người viết Kubernetes thiết kế xoay quanh khớp mẫu cấu trúc thay vì các khối if/else
    Nhiều ngôn ngữ phổ biến hỗ trợ khớp mẫu cấu trúc có công cụ kiểm tra tại thời điểm biên dịch xem việc khớp có đầy đủ hay không, và chỉ riêng điều đó cũng có thể là một giải pháp quen thuộc, đồng thời tăng mật độ thông tin của mã

  • Thảo luận năm 2018: https://news.ycombinator.com/item?id=18772873

  • Tôi chỉ lướt qua mã, nhưng thành thật mà nói trông nó không tệ đến vậy. Có những chỗ tôi sẽ làm khác, nhưng tôi đã thấy rất nhiều mã còn tệ hơn nhiều
    Ít nhất mã này tuân theo một quy tắc, mọi thứ đều có vẻ được viết sau khi đã suy nghĩ, và trong sự hỗn độn này vẫn có phương pháp riêng. Tôi sẽ luôn chọn kiểu mã này thay vì thứ hỗn tạp điển hình mà tôi đã thấy nhiều lần: trộn lẫn phong cách, code lười biếng, cấu trúc phi logic

  • Tôi thắc mắc vì sao khi tạo ra các thực hành “an toàn” mới lại bỏ qua những thực hành tốt nhất về kỹ nghệ phần mềm đã được ghi nhận
    Module 2.000 dòng, phương thức 200 dòng, và if lồng nhau 3–4 tầng được xem là có hại. Chú thích chỉ nói mã làm gì chứ không nói tại sao cũng không hữu ích và dễ lệch với mã thực tế. Tôi cũng thấy việc dùng nil không cần thiết. Chưa cần đi vào các vấn đề sâu hơn như độ kết dính hay nguyên tắc trách nhiệm đơn nhất, chỉ nhìn bề mặt đã thấy những điểm này

    • Nếu bạn nghĩ những thứ này có hại, tôi khuyên nên đọc “John Carmack on Inlined Code”
      http://number-none.com/blow/john_carmack_on_inlined_code.htm...

      “Mã điều khiển bay của tên lửa Armadillo chỉ có vài nghìn dòng, nên tôi lấy hàm tic chính và bắt đầu inline tất cả các subroutine. Tôi không thể nói rằng mình đã tìm được lỗi ẩn nào có thể gây ra một vụ rơi thật sự, nhưng tôi đã tìm thấy vài biến được thiết lập nhiều lần và vài luồng điều khiển trông hơi đáng ngờ, còn mã cuối cùng thì nhỏ hơn và gọn gàng hơn.”

      Nếu Carmack tìm thấy giá trị trong cách tiếp cận này, có lẽ ta không nên vội vàng bác bỏ. Các bình luận tiếp theo cũng đáng xem

      “Trong vài năm sau khi viết bài này, tôi đã trở nên tích cực hơn nhiều với lập trình thuần hàm trong phạm vi hợp lý, ngay cả trong C/C++... Khi mọi thứ trở nên khó kiểm soát, hãy tìm cách tách các khối thành hàm thuần”

    • Đôi khi có những trường hợp “Không có cách nào khác(TM)”
      Giới hạn số dòng tùy ý dễ dẫn đến phân mảnh không cần thiết. Cộng thêm include, giấy phép, mã kết nối, chú thích, nó sẽ trở thành spaghetti khó tiếp cận. Hãy thử giữ phương thức ở mức 200 dòng trong mã hiệu năng cao xem; hiệu năng có thể lao xuống như chuyến bay của Icarus

Khi đọc chú thích trong mã, có thể thấy tác giả đã đơn giản hóa đoạn mã này thành một mô-đun duy nhất, đồng thời đưa vào một lượng lớn bí quyết để làm cho nó dễ tiếp cận và, quan trọng hơn, bền vững. Với người không biết ngôn ngữ hoặc logic, các chú thích phác họa mã đang làm gì là cực kỳ hữu ích. Sáu tháng sau, ngay cả mã của chính mình cũng trở nên xa lạ, nên nó cũng hữu ích cho bản thân

Chú thích là một phần của mã và codebase. Nếu sửa mã xung quanh mà không cập nhật chú thích cùng lúc, tức là bạn đang đưa lỗi tài liệu vào mã. Việc trình biên dịch không xử lý chúng không có nghĩa chúng không phải là phần chức năng. Về bản chất, chú thích là tri thức, là ghi chú nghiên cứu được nhúng trong mã, và khi bảo trì mã đã viết, chúng có thể còn giá trị hơn phần mã được thực thi

Best practice không phải là luật hay quy tắc cứng nhắc, mà là hướng dẫn. Chỉ nên áp dụng khi phù hợp với codebase, chứ không nên mù quáng làm theo rồi tạo ra một codebase có vấn đề. Đôi khi cần bẻ cong quy tắc và tự tạo ra cách của mình; nếu bạn biết mình đang làm gì thì điều đó hoàn toàn chấp nhận được
  • Tôi đã viết theo kiểu "an toàn" như thế này khá lâu, nhưng nó tạo ra nhiều lỗi hơn hẳn so với xử lý lỗi kiểu đường ray bằng trả về sớm, và cũng mất nhiều thời gian hơn để sửa
    Nếu gắn else tường minh cho mọi khối if, độ phức tạp do phải ghi nhớ ngữ cảnh hiện tại sẽ bùng nổ. Tôi nghĩ nên đổi quy tắc này thành "mọi khối điều kiện if hoặc trả về sớm, hoặc có khối else tương ứng". Mẫu if (cond) { xử lý đặc biệt } chắc chắn nguy hiểm hơn nhiều và khó suy luận hơn so với trả về sớm

  • Không tồn tại một bộ best practice chính thống duy nhất
    Bản thân độ dài hàm hay số dòng mã trong một tệp không tự nó có hại hay có lợi. Mỗi ngôn ngữ có quan điểm riêng về cách tổ chức mã, nhưng không cái nào có thể được khẳng định là "thực hành tốt nhất". Go không phải là ngôn ngữ ưa chuộng việc chia mã thành rất nhiều tệp nhỏ

  • Một phương thức dài 200 dòng không tự nó là sai. Nếu mã bên trong tuyến tính và giữ cùng một mức trừu tượng, đó có thể là lựa chọn tốt nhất
    Phương án thay thế là tạo 40 phương thức, mỗi phương thức 5 dòng, có thể còn tệ hơn. Để hiểu toàn bộ, bạn phải nhảy qua nhảy lại nhiều nơi, và cũng có thể làm sai thứ tự gọi. Số hoán vị có thể chọn lên tới 40!

  • Loại mã này có vẻ là ứng viên lý tưởng để chuyển sang một hệ thống khai báo, dựa trên quy tắc, điều khiển bằng bảng
    Cách đó dễ hiểu và dễ kiểm chứng hơn so với một mớ mã mệnh lệnh chắp vá đầy các mệnh đề if. Loại mã lộn xộn như vậy thường là dấu hiệu cho thấy đang thiếu một tầng trừu tượng

    • Triết lý của Go về cơ bản gần với việc viết tuôn ra mọi thứ như thể chuyển khá trực tiếp đoạn mã vốn sẽ viết bằng C, và không cố trừu tượng hóa điều gì