69 điểm bởi xguru 2024-07-15 | 6 bình luận | Chia sẻ qua WhatsApp

Nếu bạn cứ tự bắn vào chân mình, hãy sửa khẩu súng

  • Trong đội ngũ thường có những lỗi hay gặp liên quan đến hệ thống, nhưng nhiều khi lại không nghĩ đến cách giảm bớt những lỗi đó
  • Trong những trường hợp như vậy, điều quan trọng là cải thiện hệ thống để giảm sai sót
  • Kinh nghiệm:
    • Khi phát triển iOS với CoreData, việc cập nhật UI chỉ có thể thực hiện trên main thread
    • Callback của subscription xảy ra cả trên main thread lẫn background thread nên thường xuyên phát sinh vấn đề
    • Các thành viên cũ trong nhóm đều nhận thức được điều này và xử lý tốt, nhưng trong review của người mới thì nó thường xuyên bị nhắc đến
    • Mỗi khi thỉnh thoảng có lỗi xảy ra, chúng tôi lại xem crash report rồi thêm DispatchQueue.main.async
    • Để giải quyết triệt để, tôi đã cập nhật subscription layer để subscriber luôn được gọi trên main thread. Chỉ mất đúng 10 phút.
    • Loại bỏ cả một nhóm crash và một phần gánh nặng tinh thần
  • Đây là kiểu vấn đề mà ai dành vài phút suy nghĩ cũng sẽ thấy là quá rõ ràng
  • Nhưng vì không có thời điểm tự nhiên nào để xử lý nên nó lại kéo dài một cách kỳ lạ
    • Nói cách khác, nếu ở trong nhóm đủ lâu thì những vấn đề như thế rất dễ chìm vào nền
  • Cần thay đổi tư duy
    • Thỉnh thoảng phải tự nhắc mình rằng khi gặp những vấn đề như vậy, ta hoàn toàn có thể làm cho cuộc sống của mình và của cả nhóm dễ dàng hơn

Cân bằng giữa chất lượng và tốc độ

  • Luôn tồn tại trade-off giữa tốc độ triển khai và mức độ tự tin vào độ chính xác
    • Bạn cần tự hỏi trong bối cảnh hiện tại, việc phát hành bug có chấp nhận được đến mức nào
    • Nếu câu trả lời cho việc đó không ảnh hưởng đến cách bạn làm việc, có lẽ bạn đang quá cứng nhắc
  • Ở công việc đầu tiên khi làm dự án xử lý dữ liệu, chúng tôi có một hệ thống rất tốt để reprocess dữ liệu hồi tố
    • Tác động của việc phát hành bug là rất nhỏ, và trong môi trường như vậy có thể dựa phần nào vào guardrail để di chuyển nhanh hơn
    • 100% test coverage hay quy trình QA quá rộng chỉ làm chậm tốc độ phát triển
  • Ở công việc thứ hai, sản phẩm được hàng chục triệu người dùng sử dụng, xử lý dữ liệu tài chính giá trị cao và thông tin định danh cá nhân, nên bug là điều chí mạng
    • Ngay cả bug nhỏ cũng cần làm postmortem
    • Chúng tôi phát hành tính năng rất chậm, nhưng tôi nghĩ năm đó đã không để lọt bug nào
  • Trong đa số trường hợp, bạn không ở trong bối cảnh như công ty thứ hai
    • Trong các tình huống bug không mang tính chí mạng (ví dụ: 99% web app), phát hành nhanh rồi sửa bug nhanh thường tốt hơn
    • Bạn có thể tiến xa hơn so với việc dành thời gian để ngay từ đầu phát hành một tính năng hoàn hảo

Thời gian để mài cưa gần như luôn đáng giá

  • Thành thạo công cụ là điều rất quan trọng
  • Bạn cần viết code nhanh, biết các phím tắt quan trọng, và thành thạo hệ điều hành cũng như shell
    • Bạn sẽ thường xuyên phải đổi tên, đi tới định nghĩa kiểu, tìm tham chiếu, v.v.
    • Cần biết mọi phím tắt quan trọng của editor và gõ phím tự tin, nhanh nhẹn
    • Sử dụng hiệu quả browser devtools cũng rất quan trọng
  • Chọn đúng công cụ và sử dụng thành thạo là một lợi thế lớn
  • Một trong những green flag lớn nhất tôi thấy ở kỹ sư mới là sự quan tâm đến việc chọn công cụ và dùng chúng một cách thành thạo

Nếu bạn không thể giải thích vì sao cái khó lại khó một cách đơn giản, có lẽ đó là độ phức tạp ngẫu nhiên, và vấn đề này đáng để giải quyết

  • Người quản lý tôi thích nhất có thói quen luôn ép tôi giải thích tiếp mỗi khi tôi nói rằng một việc nào đó khó triển khai
    • Thường thì phản hồi của anh ấy sẽ là kiểu như: "Chẳng phải về cơ bản chỉ là gửi X khi làm Y thôi sao?" hoặc "Chẳng phải giống Z mà mấy tháng trước mình đã làm sao?"
    • Đó là kiểu phản biện ở mức rất cao, chứ không phải ở cấp độ hàm và lớp cụ thể mà tôi đang cố giải thích
  • Quan điểm phổ biến là việc quản lý đơn giản hóa mọi thứ theo cách đó chỉ gây khó chịu
  • Nhưng với tỷ lệ đáng ngạc nhiên, khi anh ấy cứ tiếp tục hỏi, tôi nhận ra phần lớn sự phức tạp mà mình đang mô tả thực ra là độ phức tạp ngẫu nhiên
  • Và bằng cách giải quyết phần đó trước, tôi thực sự có thể biến bài toán thành chuyện nhỏ như anh ấy nói
  • Cách tiếp cận như vậy cũng có xu hướng giúp những thay đổi sau này trở nên dễ dàng hơn

Hãy cố sửa bug sâu thêm một tầng

  • Thay vì sửa bug ở bề mặt, điều quan trọng là tìm ra và xử lý nguyên nhân gốc rễ
  • Giả sử có một React component trong dashboard xử lý đối tượng User lấy từ trạng thái của người dùng đang đăng nhập
    • Trong Sentry xuất hiện bug report nói rằng usernull trong lúc render
      • Bạn có thể nhanh chóng thêm if (!user) return null hoặc
    • Nếu tìm hiểu kỹ hơn một chút, bạn sẽ thấy hàm logout thực hiện hai lần cập nhật state riêng biệt
      • Lần đầu đặt người dùng thành null, lần thứ hai redirect về trang chủ
    • Chỉ cần đổi thứ tự của hai việc đó, sẽ không còn component nào gặp lại bug này nữa
    • Vì bên trong dashboard, đối tượng người dùng lẽ ra không bao giờ là null
  • Nếu bạn cứ tiếp tục kiểu sửa bug thứ nhất thì hệ thống sẽ thành một mớ hỗn độn, nhưng
    khi tiếp tục kiểu sửa bug thứ hai, bạn sẽ có một hệ thống sạch sẽ và hiểu sâu về tính bất biến

Đừng đánh giá thấp giá trị của việc đào lại history để điều tra bug

  • Tôi từng khá giỏi debug những vấn đề kỳ quặc bằng các công cụ thông thường như println và debugger
  • Vì vậy tôi không hay dùng git để lần lại lịch sử của bug, nhưng với một số bug thì việc này lại cực kỳ quan trọng
  • Gần đây có vấn đề server dường như bị memory leak liên tục, bị OOM kill rồi khởi động lại
    • Mọi nguyên nhân có vẻ hợp lý đều đã bị loại trừ, và cũng không thể tái hiện trên local
    • Cảm giác như đang bịt mắt ném phi tiêu
    • Khi xem commit history, tôi thấy nó bắt đầu xảy ra sau khi thêm hỗ trợ thanh toán Play Store
    • Chỉ là vài HTTP request nên có lẽ cả triệu năm tôi cũng không nghĩ tới việc kiểm tra chỗ đó
    • Hóa ra hệ thống rơi vào vòng lặp vô hạn khi lấy access token sau khi access token đầu tiên hết hạn
    • Mỗi request có thể chỉ thêm khoảng 1kB bộ nhớ, nhưng nếu retry mỗi 10ms trên nhiều thread thì nó tích lại rất nhanh
    • Bình thường chuyện như vậy sẽ gây stack overflow, nhưng vì tôi đang dùng async recursion trong Rust nên stack overflow đã không xảy ra
    • Có lẽ tôi sẽ không bao giờ nghĩ ra điều này, nhưng khi nhìn vào đoạn code cụ thể rõ ràng đã gây ra vấn đề, một giả thuyết bỗng xuất hiện
  • Không có quy tắc nào cho việc khi nào nên dùng cách tiếp cận này
    • Nó dựa nhiều vào trực giác; một kiểu "hả?" khác thường trong bug report có thể kích hoạt kiểu điều tra này
    • Theo thời gian bạn có thể phát triển trực giác đó, nhưng chỉ cần biết rằng nó đôi khi cực kỳ giá trị cũng đã đủ
  • Khi phù hợp, hãy thử git bisect
    • Khi bạn có một commit chắc chắn là lỗi và một commit chắc chắn là tốt

Code tệ tạo ra phản hồi, còn code hoàn hảo thì không. Hãy nghiêng về phía viết code tệ

  • Viết ra code tệ hại thì rất dễ
  • Nhưng viết code tuân thủ tuyệt đối mọi best practice cũng dễ không kém
    • Bạn sẽ còn phải đi qua unit test, integration test, fuzz test, mutation test, và startup thì sẽ cạn tiền trước khi xong mất
  • Phần lớn công việc lập trình là tìm điểm cân bằng
  • Nếu bạn nghiêng về phía viết code nhanh...
    • Thỉnh thoảng bạn sẽ gặp rắc rối vì technical debt tồi tệ
    • Bạn sẽ học được rằng "mình cần thêm test thật tốt cho xử lý dữ liệu"
      • Vì về sau sửa thường là không thể
    • Bạn cũng sẽ học được rằng "mình phải suy nghĩ rất kỹ về thiết kế bảng"
      • Vì thay đổi mà không có downtime có thể rất khó
  • Nếu bạn nghiêng về phía viết code hoàn hảo...
    • Bạn sẽ không nhận được phản hồi nào
    • Mọi thứ đều mất thời gian một cách đồng loạt
    • Bạn không biết đâu là chỗ mình đang đầu tư thời gian đúng, và đâu là chỗ đang lãng phí thời gian
    • Cơ chế phản hồi là yếu tố thiết yếu để học hỏi, nhưng bạn lại không có được nó
  • Làm rõ ý nghĩa của code "tệ"
    • Không phải kiểu "tôi không nhớ cú pháp tạo hashmap nên đã dùng vòng lặp bên trong hai lần"
    • Ý ở đây là những việc như:
      • Thay vì viết lại việc thu thập dữ liệu để khiến một trạng thái nào đó không thể biểu diễn được, hãy thêm vài assertion về tính bất biến ở một số checkpoint quan trọng
      • Vì model của server giống hệt DTO sẽ được ghi ra, cứ serialize luôn. Thay vì viết toàn bộ boilerplate, có thể tạo DTO sau nếu thực sự cần
      • Các component này rất nhỏ và nếu có bug cũng không quá nghiêm trọng, nên bỏ qua việc viết test

Hãy làm cho việc debug trở nên dễ dàng

  • Qua nhiều năm, tôi tích lũy được rất nhiều mẹo nhỏ để khiến phần mềm dễ debug hơn
    • Nếu bạn không chủ động đầu tư để việc debug dễ hơn, khi phần mềm ngày càng phức tạp, bạn sẽ tiêu tốn lượng thời gian khổng lồ chỉ để debug từng issue
    • Bạn sẽ bắt đầu sợ thay đổi. Vì chỉ để tìm ra vài bug mới thôi cũng có thể mất cả tuần
  • Hãy để ý xem trong thời gian debug, bạn mất bao nhiêu cho setup, tái hiện và dọn dẹp hậu quả
    • Nếu con số đó trên 50%, thì dù lần này có mất lâu hơn một chút, bạn vẫn nên tìm cách làm cho nó dễ hơn về sau
    • Nếu các điều kiện khác như nhau, bug fix theo thời gian lẽ ra phải ngày càng dễ hơn

Khi làm việc trong team, hãy luôn đặt câu hỏi

  • Có cả một phổ từ "cố tự mình tìm ra mọi thứ" đến "làm phiền đồng nghiệp bằng những câu hỏi vụn vặt"
    • Tôi nghĩ phần lớn người mới vào nghề đều lệch quá nhiều về phía đầu tiên
  • Xung quanh bạn luôn có người đã ở trong codebase lâu hơn, hoặc hiểu kỹ hơn nhiều về công nghệ X, hoặc hiểu sản phẩm hơn, hoặc đơn giản là kỹ sư có nhiều kinh nghiệm hơn
  • Trong 6 tháng đầu ở một nơi nào đó, bạn thường sẽ mất hơn một giờ loay hoay tìm hiểu một việc mà lẽ ra có thể nhận được câu trả lời chỉ trong vài phút
  • Hãy hỏi. Việc đặt câu hỏi chỉ thực sự gây phiền khi quá rõ ràng rằng bạn hoàn toàn có thể tự tìm ra câu trả lời trong vài phút

Chu kỳ triển khai cực kỳ quan trọng. Hãy suy nghĩ kỹ về cách để có thể deploy nhanh và thường xuyên

  • Startup có runway hữu hạn, và dự án thì có deadline
  • Khi nghỉ việc để tự làm riêng, số tiền tiết kiệm của bạn cũng chỉ đủ kéo dài vài tháng
  • Lý tưởng nhất là tốc độ của dự án tăng theo kiểu lãi kép theo thời gian, để bạn triển khai tính năng nhanh hơn cả những gì mình từng tưởng tượng
  • Để deploy nhanh, cần rất nhiều yếu tố
    • Một hệ thống không quá dễ dính bug
    • Turnaround nhanh giữa các team
    • Sẵn sàng cắt bỏ 10% của tính năng mới (phần sẽ ngốn 50% thời gian kỹ thuật) và có đủ insight để biết đó là phần nào
    • Những pattern nhất quán, tái sử dụng được để lắp ghép cho màn hình/tính năng/endpoint mới
    • Triển khai nhanh và dễ
    • Quy trình không làm chậm lại (test không ổn định, CI chậm, linter quá khó tính, PR review chậm, JIRA bị tôn thờ như tôn giáo, v.v.)
    • Và hàng triệu thứ khác nữa
  • Triển khai chậm cũng nên được làm postmortem nghiêm túc chẳng kém gì việc làm sập production
    • Ngành của chúng ta không vận hành như vậy, nhưng điều đó không có nghĩa là cá nhân bạn không thể lấy việc deploy nhanh làm kim chỉ nam

6 bình luận

 
carnoxen 2024-07-19

"Tự bắn vào chân mình" = nghĩa là tự chuốc lấy khổ vào thân phải không?

 
yunghn 2024-07-25

Nếu đoạn mã có vấn đề (khẩu súng bị hỏng) gây ra rắc rối (tự bắn vào chân mình), thì điều cần nói là hãy sửa khẩu súng.

 
gargoyle92 2024-07-16

Cú sốc như thể moi nguyên đống suy nghĩ trong đầu mình ra luôn, run quá..

 
cbbatte 2024-07-16

Đọc rất hay!!

 
hannah0su 2024-07-15

Đã đọc rất kỹ.

 
arfwene 2024-07-15

Không phải là lập trình viên nhưng có khá nhiều điểm thấy đồng cảm.