Đừng đơn giản hóa đoạn mã này
(github.com/kubernetes)pv_controller.gocủ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
elsetương ứng cho mọiifvà để 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.VolumeNamevàpv.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.golà tệp triển khai PersistentVolumeController trong packagepersistentvolumecủa Kubernetes- Controller này đồng bộ trạng thái của
PersistentVolumeClaimvàPersistentVolume- 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
- Cache controller theo dõi thay đổi của
- 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
elsetương ứng cho mọi câu lệnhif - 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
- Tên phong cách là
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 stylevà 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
- Con trỏ phía PVC:
- 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
- Trước tiên sửa
- Ở 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
PersistentVolumeControllercó 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 persistentVolumeOrderedIndexclaims 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
- Cập nhật
- 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
claimQueuevolumeQueue
- 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
syncClaimlà 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
- Nếu không có annotation:
- 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
checkVolumeSatisfyClaimkiể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
storageClassNamekhác nhau - Nếu feature gate
VolumeAttributesClassbật, kiểm traVolumeAttributesClassNamecó 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
volumeModekhông tương thích - Lỗi nếu access mode không tương thích
- Lỗi nếu PV có
- Nếu vượt qua mọi điều kiện, trả về
nil
Xử lý event cho PVC delayed binding
emitEventForUnboundDelayBindingClaimtạ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.VolumeNametrố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
FailedBindingrằ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
syncClaimsau sẽ hoàn tất binding
Xử lý PVC yêu cầu PV cụ thể
- Nếu
claim.Spec.VolumeNamekhô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
Pendingvà thử lại sau - Nếu PV được yêu cầu tồn tại và
volume.Spec.ClaimRefkhô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
VolumeMismatchvà giữ PVC ởPending - Nếu đáp ứng, gọi
bind
- Kiểm tra yêu cầu bằng
- 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
FailedBindingvà để ở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”
- Nếu claim không có annotation cho biết đã được controller bind, ghi event
syncBoundClaim: xử lý PVC đã được bind
syncBoundClaimxử lý PVC có annotationAnnBindCompleted- Nếu claim đã được bind nhưng
claim.Spec.VolumeNametrống, đổi trạng thái claim thànhClaimLost- 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.ClaimRefkhông có, xem như volume đã chuyển sang trạng thái unbound và gọi lạibind - Nếu
ClaimRef.UIDcủa PV giống UID của claim, xem là trạng thái binding bình thường và gọibind- 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
syncVolumelà 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.ClaimRefkhông có, xem đây là volume chưa được dùng và đặt phase thànhAvailable - Nếu có
ClaimRefnhưng UID trống, xem đây là PV được đặt trước cho một PVC cụ thể và đặt phase thànhAvailable- 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
Releasedvà chạyreclaimVolume- Nếu phase hiện tại là
Failedthì 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
- Nếu phase hiện tại là
Khi liên kết PV và PVC bị lệch
- Nếu claim tồn tại nhưng
claim.Spec.VolumeNametrống, nghĩa là PVC chưa có tên PV - Nếu
volumeModekhông khớp, ghi eventVolumeMismatchở cả PV và PVC, rồi bỏ quasyncClaim - Nếu không mismatch, thêm claim vào
claimQueueđểsyncClaimsớm được gọi- Cách này giúp binding của provisioned volume diễn ra nhanh hơn
- Nếu
Spec.VolumeNamecủ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ànhBound - 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ấuReleasedvà chạyreclaimVolume - 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óaClaimRef.UID
- Nếu là volume được provision động và reclaim policy là
Cập nhật trạng thái và phát event
updateClaimStatuslưu status của PVC vào API server- Thay đổi phase
- Khởi tạo
AccessModes,Capacity,CurrentVolumeAttributesClassNamekhi 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
VolumeAttributesClassbậ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
updateClaimStatusWithEventvàupdateVolumePhaseWithEventchỉ phát event khi status/phase thực sự thay đổi
Gán StorageClass mặc định
assignDefaultStorageClasstì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.StorageClassNamevà 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.gocó 2038 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
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
iflồ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íchelsetương ứng dường như không phải lúc nào cũng đúng. Nhiềuifkhông có phần tương ứng chỉ là các kiểm tra đơn giản kiểuif (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ácifkhô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 iflan 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.
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.
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:
Ô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...
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 nevermớ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ỗiif elseBạn cũng có thể thích
ts-patternhttps://github.com/gvergnaud/ts-pattern
Chỉ xét các trường hợp gắn
elsetường minh cho mọiifkhô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ốiif/elseNhiề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à
iflồ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ùngnilkhô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àyNế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
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
elsetường minh cho mọi khốiif, độ 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ệnifhoặc trả về sớm, hoặc có khốielsetương ứng". Mẫuif (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ớmKhô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