52 điểm bởi GN⁺ 2025-02-06 | 6 bình luận | Chia sẻ qua WhatsApp
  • Nội dung chia sẻ của các lập trình viên giàu kinh nghiệm với lập trình viên mới về triết lý phát triển phần mềm
  • Bao gồm lời khuyên về nhiều chủ đề như tránh viết lại toàn bộ từ đầu (ground-up rewrite), quản lý tiến độ, chất lượng mã, tự động hóa và xử lý edge case

Hãy tránh những tình huống mà việc viết lại toàn bộ từ đầu (ground-up rewrite) trông có vẻ hấp dẫn, bằng mọi giá

  • Sự cám dỗ của việc viết lại toàn bộ xuất hiện khi nợ kỹ thuật đã tích tụ đến mức việc duy trì mã hiện có trở nên khó khăn
  • Cần phát hiện sớm các tín hiệu cảnh báo khi độ phức tạp của mã bắt đầu tích lũy (ngay cả chỉnh sửa đơn giản cũng khó, khó viết chú thích/tài liệu, số người hiểu mã cốt lõi giảm, v.v.) và chủ động tìm giải pháp
  • Sau khi hoàn tất mở rộng, nhất định phải có giai đoạn tích hợp để giảm độ phức tạp và chỉnh đốn chất lượng
  • Nếu việc viết lại toàn bộ trở nên không thể tránh khỏi, điều đó có nghĩa là dự án đã bước vào giai đoạn rủi ro
  • Để giảm rủi ro, cần liên tục quản lý nợ kỹ thuật và theo dõi chặt chẽ chất lượng mã

Hãy hoàn thành 90% toàn bộ công việc trong một nửa thời gian có thể dùng

  • Việc viết mã bản đầu tiên để nó chạy được chỉ tương đương khoảng một nửa tổng khối lượng công việc
  • Những giai đoạn sau đó (xử lý edge case, kiểm thử, triển khai, tài liệu hóa, hiệu năng, khả năng bảo trì, v.v.) đòi hỏi nhiều thời gian hơn tưởng tượng để hoàn tất đúng cách
  • Cần dành đủ vùng đệm để ứng phó với các vấn đề bất ngờ hoặc các công việc hoàn thiện cuối cùng (polishing)
  • Cuối cùng, phải nhận thức được khoảng cách giữa việc “làm cho đoạn mã ban đầu chạy tạm được” và “biến nó thành một tính năng hoàn chỉnh”, rồi phản ánh điều đó vào kế hoạch

Hãy tự động hóa các thực hành tốt

  • Nếu chỉ lặp đi lặp lại bằng lời nói hoặc tài liệu rằng “phải làm như thế này”, sai sót rất dễ xảy ra
  • Khi có thể, việc cưỡng chế bằng kiểm thử tự động hoặc script theo kiểu “vi phạm quy tắc thì build thất bại” sẽ hiệu quả hơn
  • Với các quy tắc mới được đưa vào (API bị cấm, chú thích bắt buộc phải có, v.v.), cũng có thể tự động hóa dần để giảm lỗi và thiếu sót
  • Tuy nhiên, không phải mọi thứ đều có thể tự động hóa, và các quy tắc quá nghiêm ngặt có thể cản trở luồng phát triển, nên cần sự cân bằng

Hãy cân nhắc cả dữ liệu cực đoan (bệnh lý/Pathological)

  • Sẽ rất nguy hiểm nếu chỉ đánh giá mã bằng đầu vào bình thường (golden path)
  • Cần giả định các tình huống có vấn đề như request bị chậm hoặc bị ngắt, dữ liệu khổng lồ (hàng triệu đến hàng trăm triệu dòng), chuỗi ký tự kỳ lạ (quá dài, chứa dấu gạch chéo hoặc khoảng trắng), v.v.
  • Chuẩn bị kỹ cho edge case là yếu tố quyết định chất lượng mã cuối cùng

Thường sẽ có một cách viết đơn giản hơn

  • Sau khi làm cho mã chạy được ở giai đoạn đầu, nên dành thời gian nhìn lại để cải thiện nó theo hướng đơn giản và rõ ràng hơn
  • Ngay cả khi đã tìm ra một “giải pháp trông có vẻ tốt”, thái độ quan trọng là vẫn dành thời gian xem còn giải pháp nào tốt hơn không
  • Quá trình refactor mã dài và phức tạp thành ngắn gọn sẽ nâng cao chất lượng cuối cùng

Hãy viết mã sao cho có thể kiểm thử

  • Nếu tối thiểu hóa interface và side effect, việc viết kiểm thử tự động sẽ dễ dàng hơn rất nhiều
  • Nếu cấu trúc quá khó kiểm thử, rất có thể việc đóng gói (encapsulation) chưa được làm đúng
  • Nếu thiết kế cấu trúc mã theo cách cụ thể để việc kiểm thử diễn ra tốt, có thể phát hiện bug từ sớm

Mã không kết thúc ở chỗ “về mặt chứng minh là không có vấn đề”

  • Ngay cả mã có vẻ an toàn về mặt cấu trúc cũng có thể phát sinh vấn đề khi môi trường xung quanh thay đổi hoặc cách gọi nào đó bị thay đổi
  • Nếu là mã liên quan đến bảo mật, cần thiết kế có tính đến khả năng thay đổi trong tương lai, ngay cả khi chỗ gọi hiện tại là an toàn
  • Mã cần được viết sao cho “rõ ràng là an toàn và dù có thay đổi cũng không phát sinh vấn đề”

Bình luận

  • Câu “Lý do tôi không thể viết bức thư này ngắn hơn là vì tôi không có thời gian để viết nó ngắn hơn” có nguồn gốc từ Pascal
  • Luôn cảnh giác với lỗi off-by-one error
  • Thêm hướng dẫn mới vào wiki
    • Nếu hệ thống tài liệu hóa/chia sẻ tri thức trong công ty không được thiết lập đúng, thông tin sẽ bị phân tán và gây hỗn loạn
    • Có thể phát sinh các vấn đề như tài liệu chính thức trở nên lỗi thời vì phải qua quy trình phê duyệt, hoặc có nhiều wiki cùng tồn tại khiến không rõ đâu là tài liệu chuẩn
    • Cách hiệu quả là chỉ định một wiki duy nhất để tập hợp toàn bộ tài liệu, và nếu còn thiếu thì các lập trình viên trực tiếp viết thêm hoặc bổ sung thông qua reverse-engineering
    • Nếu tài liệu hóa được liên kết tốt với các nơi khác (source control, chú thích trong mã, v.v.) thì sẽ dễ duy trì thông tin mới nhất hơn
    • Nếu kiểm thử tự động và môi trường build quá phức tạp hoặc không được công khai cho lập trình viên, có thể xuất hiện tình huống chưa từng thực sự chạy toàn bộ test
    • Nên giữ script build Jenkins đơn giản, theo kiểu cd project; ./build-it, và cũng nên đưa chính script này vào source control
    • Nếu cả nhóm cùng chia sẻ để có thể build và test trong cùng một môi trường (ví dụ: image máy ảo, cấu hình build), có thể giảm trước các vấn đề phát sinh
    • Sẽ hữu ích nếu cân nhắc edge case ngay từ giai đoạn phát triển, chẳng hạn để destroy_object(foo) vẫn hoạt động an toàn ngay cả khi foo là NULL, hoặc để create_object() khi thất bại sẽ nội bộ gọi destroy_object()
    • Cuối cùng, điều quan trọng là mọi lập trình viên đều có thể dễ dàng truy cập và tham gia vào tài liệu cũng như môi trường build/test
  • Tài liệu hóa và kiểm thử tự động: có nhắc đến đề xuất thực tế rằng tài liệu hoặc wiki để chia sẻ tri thức rất quan trọng, và cấu hình môi trường CI/CD như Jenkins cũng cần có thể chia sẻ được
  • Không có công cụ thay đổi hành vi nào hiệu quả hơn việc “gây bất tiện” cho đến khi mọi người ghi nhớ hành vi mong muốn
  • Cũng như trong cờ vua có ý kiến phản đối câu châm ngôn “Khi thấy một nước đi có vẻ tốt, hãy cứ thực hiện nó”, lập trình cũng vậy. Nó có tính hai mặt.

6 bình luận

 
bbulbum 2025-02-07

> Thông thường sẽ có cách viết đơn giản hơn

Tôi tin rằng việc suy nghĩ về một giải pháp gọn gàng hơn sẽ giúp giảm độ phức tạp của mã nguồn và chính sách.

 
kipsong133 2025-02-06

> "Hoàn thành 90% toàn bộ công việc trong một nửa thời gian có thể"

Tôi nghĩ nội dung này thực sự rất đúng. Thay vì cố gắng triển khai hoàn hảo ngay trong một lần, việc nhanh chóng làm đến vòng thứ hai lại giúp giảm sai sót và quản lý thời gian tốt hơn.

 
jhj0517 2025-02-06
  1. Tự động hóa các thực tiễn tốt nhất (pipeline CI/CD cho mã kiểm thử)
  2. Nên làm cho mã chạy được ở giai đoạn đầu trước, rồi sau khi có thêm dư địa về thời gian thì quay lại cải thiện để nó đơn giản và rõ ràng hơn
  3. Dù trong tình huống nào cũng hãy tránh việc viết lại toàn bộ từ đầu (ground-up rewrite) khi nó trông có vẻ hấp dẫn
 
winterjung 2025-02-06

> Luôn cẩn thận với lỗi off-by-one

Ban đầu tôi không rõ lỗi off-by-one là gì, hóa ra đó là nhóm lỗi liên quan đến điều kiện biên như for (int i = 1; i < n; ++i) { ... }, for (int i = 0; i <= n; ++i) { ... }.

 
ethanhur 2025-02-06

> Bằng mọi giá, hãy tránh những tình huống mà việc viết lại toàn bộ từ đầu (ground-up rewrite) trông có vẻ hấp dẫn.

Có vẻ đây là một pitfall khiến hoạt động kinh doanh của nhiều công ty IT không thể phát triển, và khi technical debt đã tích tụ quá nhiều mà tổ chức kỹ thuật không thể giải quyết được (hoặc không muốn giải quyết), họ thường xem việc viết lại toàn bộ là một lựa chọn hấp dẫn.

Tôi rất đồng cảm với quan điểm của các tác giả rằng nếu không có lý do thực sự đủ sức thuyết phục thì nên tránh việc viết lại càng nhiều càng tốt.

 
GN⁺ 2025-02-06
Ý kiến trên Hacker News
  • Phát triển phần mềm là một quá trình lặp đi lặp lại giữa thử nghiệm và học hỏi. Các lập trình viên giàu kinh nghiệm nâng cao mức độ hiểu biết thông qua việc viết mã và kiểm thử, đồng thời học được rất nhiều, nhưng điều này thường không thể hiện rõ trong các cuộc họp hay kế hoạch. Lập trình viên junior gặp khó khăn trong việc tạo ra mã sẵn sàng cho production và ngại làm những phần việc có thể bị bỏ đi. Nếu người quản lý thiếu kinh nghiệm phát triển, vấn đề này có thể trở nên tệ hơn

  • Câu trích dẫn "Tôi xin lỗi vì đã viết một bức thư dài, nhưng tôi không có thời gian để viết ngắn hơn" là lời của nhà toán học và triết gia người Pháp Blaise Pascal, mang ý rằng viết ngắn gọn thực ra khó hơn

  • Quy tắc 90/50 nhấn mạnh rằng để nâng cao chất lượng mã, cần coi trọng kiểm thử và xử lý ngoại lệ. Việc thiết lập kiểm thử tự động giúp xác định các kỳ vọng rõ ràng cho codebase

  • Giáo dục khoa học máy tính thường dẫn người học tới việc viết mã phức tạp, nhưng điều quan trọng là phải viết mã dễ đọc. Cần có cách đặt tên biến và hàm phù hợp, định dạng nhất quán và thiết kế mô-đun

  • Một cơ chế gọi là Ratchet cung cấp một phương pháp hoàn hảo cho tương lai

  • Nỗ lực khái quát hóa kinh nghiệm và nhận thức về domain có thể dẫn tới những khái quát sai lầm. Phát triển là nghệ thuật quản lý sự phức tạp, và điều quan trọng là phải học từ thất bại

  • Câu trích dẫn "90% đầu tiên của công việc chiếm 90% thời gian, và 10% còn lại lại chiếm thêm 90% thời gian nữa" phản ánh rất đúng thực tế phát triển. Khi phải tính đến xử lý ngoại lệ, lỗi, tính khả dụng, bảo mật và nhiều yếu tố khác, sẽ phát sinh rất nhiều việc ngoài dự kiến

  • Bài viết của Joel Spolsky cảnh báo về rủi ro của việc viết lại từ đầu và nhấn mạnh sự khôn ngoan của DevOps

  • Cần tối ưu hóa tính dễ đọc của mã và nhận thức rằng thời gian từ lúc bug xuất hiện đến khi được phát hiện càng dài thì chi phí càng tăng. Việc ưu tiên các nguyên tắc lập trình hàm và sử dụng hệ thống kiểu mạnh là có lợi