31/08/2018, 15:36

Lập trình viên cần học những gì từ bug

Lập trình viên cần học những gì từ bug? Đó là một câu hỏi không mấy vui vẻ, bởi có lẽ hầu hết lập trình viên đều muốn làm tính năng mới, chứ chả mấy ai thích phải bảo trì sản phẩm có sẵn hay là fix bug. Song, với cá nhân tôi, việc tìm và fix bug đem lại rất nhiều niềm vui cũng như cơ hội học ...

lap-trinh-vien-can-hoc-nhung-gi

Lập trình viên cần học những gì từ bug? Đó là một câu hỏi không mấy vui vẻ, bởi có lẽ hầu hết lập trình viên đều muốn làm tính năng mới, chứ chả mấy ai thích phải bảo trì sản phẩm có sẵn hay là fix bug.

Song, với cá nhân tôi, việc tìm và fix bug đem lại rất nhiều niềm vui cũng như cơ hội học hỏi, phát triển nghề nghiệp. Sau đây là một số tổng kết của tôi về:

  • 4 lợi ích của việc fix bug.
  • Cách ghi lại bug hiệu quả.
  • 3 bài học lớn và 18 kinh nghiệm xương máu về fix bug.

Xem việc làm Developer chất tại ITviec

Read the English version here.

I. LẬP TRÌNH VIÊN CẦN HỌC NHỮNG GÌ – LỢI ÍCH CỦA FIX BUG

Trong mỗi trường hợp, bạn đều có thể học đôi điều về phong cách lập trình, sản phẩm, hoặc về lĩnh vực mà phần mềm đang hoạt động.

Trên hết, có 4 lí do chính, cũng là 4 niềm vui quan trọng nhất mà việc fix bug có thể đem lại cho lập trình viên như sau:

1. Mỗi bug luôn dạy bạn điều gì đó.

Feedback luôn là chìa khóa của phát triển sản phẩm, đồng thời cũng là triết lý cốt lõi của mô hình agile.

Có thể có rất nhiều nguyên nhân gây ra một bug. Ví dụ:

  • Bạn có các câu lệnh if lồng nhau và vô tình lại đặt lệnh else ở sai nhánh.
  • Giả định không chính xác (chẳng hạn: truy xuất một thuộc tính không tồn tại, thế là dính NullPointerException).
  • Không bao quát hết các trường hợp (chẳng hạn, bạn phải trả về một giá trị khác đi nếu hàm được gọi với tham số X)
  • Hoặc, khách hàng sử dụng phần mềm theo cách mà bạn không ngờ tới (nhưng vẫn hợp lệ), và thế là bùm! Dính bug!

Đào sâu tìm hiểu nguyên nhân gây ra bug, bạn sẽ đúc rút được nhiều bài học quý giá.

2. Code của bạn sẽ dễ debug hơn.

Một khi đã phải bỏ công sức, thời gian ra để tìm và fix bug, tự khắc bạn sẽ muốn viết code càng dễ debug càng tốt. Bởi vì sẽ rất khốn khổ nếu không có mọi dữ liệu cần thiết.

Như vậy, mỗi khi tìm và fix bug, bạn cần tự hỏi: liệu có thể thay đổi điều gì trong code để sau này không gặp phải những bug dạng này không? Liệu có cách nào hoặc điều gì mình nên làm, để sau này tìm ra những bug dạng này dễ dàng hơn không?

3. Fix bug đem lại niềm vui cho cả bạn và khách hàng.

Một trong những niềm vui mà công việc lập trình mang lại, theo tôi, đó là làm điều có ích cho người khác. Fix bug cũng đem đến niềm vui tương tự, và thậm chí còn nhanh chóng hơn.

Fix bug cũng đem lại niềm vui cho khách hàng (dù nghe có vẻ oái oăm: nếu ngay từ đầu không có bug, không phải fix bug, thì chẳng phải khách hàng sẽ vui hơn sao?) Nhưng, từ kinh nghiệm hơn 20 năm lập trình và “chiến đấu” với bug, tôi dám khẳng định: khách hàng thực sự hài lòng mỗi khi nhận được bug được fix xong nhanh chóng.

4. Niềm vui của việc giải câu đố.

lap-trinh-vien-can-hoc-nhung-giRất nhiều lập trình viên thích giải câu đố, như chơi trò Sudoku, giải ô chữ, giải đố vui toán học, hay tham gia các thử thách lập trình.

Thậm chí, đọc truyện trinh thám giết người cũng đem lại rất nhiều hứng khởi: bạn lần theo các manh mối để tìm hiểu mọi chuyện đã diễn ra như thế nào.

Debug và fix bug cũng vậy. Mỗi bug là một bí ẩn cần khám phá.

II. LẬP TRÌNH VIÊN CẦN HỌC NHỮNG GÌ – CÁCH GHI LẠI BUG

Làm thế nào để học hỏi hiệu quả nhất từ những bug chúng ta đã fix? Phương pháp mà tôi dùng là luôn dành ra vài phút để ghi chú lại các thông tin: mô tả bug, cách fix, bài học kinh nghiệm.

Nguyên tắc:

  • Chỉ ghi chú những bug khó nhằn hoặc thực sự thú vị. Đây không phải là bug tracker.
  • Ghi chú những bug do chính mình gây ra. (Trừ trường hợp bug của người khác nhưng đủ thú vị).
  • Ghi lại bug ngay sau khi fix xong. Tránh nhớ nhầm, nhớ không chi tiết.

Cách ghi lại bug:

Tôi thường dùng form dưới đây để ghi lại bug dưới dạng file text (bugs.txt).

Ví dụ:

  • Ngày: 2004-08-17
  • Triệu chứng: Vòng lặp vô tận khi giải mã tín hiệu Q.931.
  • Nguyên nhân: Khi tìm thấy id của một thành phần chưa biết trong tín hiệu Q.931, ta tìm cách bỏ qua nó bằng cách lấy chiều dài, và di chuyển con trỏ pos tương ứng với độ dài tìm được. Tuy nhiên, với trường hợp độ dài bằng 0 làm ta liên tục bỏ qua cùng 1 id.
  • Cách tìm ra: Nhờ vào phân tích tín hiệu SETUP lấy từ trace của Ethereal ở Nortel. Tín hiệu của họ có độ dài 1016 bytes, nhưng MSX_MAX_LEN chỉ có 1000. Bình thường ta sẽ nhận một tín hiệu bị cắt từ common/Communication.cxx, nhưng ở đây khi cung cấp dữ liệu trực tiếp để phân tích, khoảng bộ nhớ vượt quá array bị truy cập, và vô tình nó bằng 0, làm xuất hiện lỗi. Để sửa lỗi, tôi đã thêm vào vài lệnh print trong phần code phân tích Q.931. Nhưng may mắn là dữ liệu lại bằng 0.
  • Sửa: Nếu chiều dài tìm thấy bằng 0, đặt nó lại bằng 1. Như vậy chúng ta sẽ luôn đi tiếp được.
  • Sửa trong file(s): callh/q931_msg.cxx
  • Thủ phạm là tôi: Đúng vậy.
  • Thời gian sửa bug: 1 giờ.
  • Bài học: Đặt “niềm tin lầm chỗ” vào dữ liệu của tín hiệu gửi tới. Giá trị dữ liệu có thể quá lớn làm chương trình chạy sai. Ngoài ra khi chiều dài bằng 0 cũng có thể là một dấu hiệu xấu.

III. LẬP TRÌNH VIÊN CẦN HỌC NHỮNG GÌ – 3 BÀI HỌC LỚN

1. Về coding.

lap-trinh-vien-can-hoc-nhung-gi

Những lỗi phạm phải trong code? Có phải đã quên một else-part? Có phải một lệnh gọi hệ thống bị thất bại, nhưng hồi đáp chưa được check? Có cách nào chỉnh sửa code để tránh những vấn đề này trong tương lai không?

  • Trình tự sự kiện.

Khi xử lý sự kiện, những câu hỏi sau sẽ rất có ích:

  • Quá sớm.

Cái này là một trường hợp đặc biệt của phần “Trình tự sự kiện” ở trên, nhưng bởi vì nó gây ra một số lỗi rất khó tìm, nên nó được đặt ra riêng.

  • “Cái chết êm đềm”.

Một trong số những lỗi khó phát hiện nhất là khi chúng lặng lẽ ra đi và chương trình tiếp tục được thực thi mà không quăng ra exception nào.

Chương trình tiếp tục chạy trong trạng thái sai, làm cho debug càng khó hơn. Nói chung tốt nhất là một lỗi nên được quăng ra càng sớm càng tốt.

  • If.

Lệnh if với nhiều điều kiện, if (a or b), đặc biệt là khi được nối lại với nhau, if (x) else if (y), gây ra quá trời lỗi cho tôi.

Dù cho câu lệnh if về mặt khái niệm quá đơn giản đi, chúng vẫn dễ bị sai khi có nhiều điều kiện đi kèm.

Bây giờ tôi cố gắng viết code đơn giản hơn để tránh phải xử lý những câu if phức tạp.

  • Else.

Cũng có quá trời lỗi là do không xét đến trường hợp bỏ qua lệnh else. Gần như tất cả trường hợp, luôn phải có một lệnh else cho mỗi câu if. Hơn nữa, nếu bạn đặt một biến bên trong lệnh if, khả năng cao là bạn phải đặt nó ở những chỗ khác nữa.

  • Thay đổi các giả định.

Những lỗi khó phòng tránh nhất trong giai đoạn đầu thường là do thay đổi giả định.

Nói chung không khó để tìm tất cả các phần phụ thuộc hiển nhiên, nhưng cái khó là tìm ra những phần phụ thuộc tiềm ẩn bên trong thiết kế cũ.

Tôi không biết cách nào tốt để đề phòng những trường hợp này, nếu bạn nào biết thì gợi ý giùm với.

  • Logging.

Điều tối quan trọng là có nhận thức về những gì chương trình hoạt động, đặc biệt trong những chương trình có logic phức tạp.

Cần chắc chắn logging được đặt vừa đủ và đúng chỗ, để bạn có thể lý luận tại sao chương trình lại chạy như vậy.

Khi mọi thứ hoạt động trơn tru thì không sao, nhưng ngay khi chương trình xảy ra lỗi (chuyện không thể tránh khỏi), ít ra bạn sẽ thấy hạnh phúc vì đã logging đúng chỗ.

2. Về Testing.

lap-trinh-vien-can-hoc-nhung-gi

Có những bug rõ ràng nên được “khui” ra ngay trong quá trình test. Nếu vậy, phần test nào đã thiếu sót – unit, functional, hay system? Test case nào đã bị thiếu?

  • 0 và null.

Luôn chắc chắn kiểm tra với giá trị 0 và null (nếu có thể). Đối với chuỗi, cần lưu ý chuỗi rỗng, và chuỗi là null.

Một ví dụ khác: kiểm tra trường hợp đứt kết nối TCP trước khi bất cứ dữ liệu (zero bytes) nào được gửi.

Bỏ qua việc kiểm tra các trường hợp trên là lý do số một làm cho bug lọt khỏi phần test của tôi.

  • Thêm vào và xóa đi.

Thường các tính năng mới sẽ dính tới chuyện thêm các thiết lập mới vào hệ thống, chẳng hạn như một kiểu định dạng mới số điện thoại.

Thường thì bạn sẽ kiểm tra xem có thể thêm định dạng mới hay không, nhưng tôi thấy là rất dễ quên kiểm tra trường hợp xóa định dạng cũ.

  • Xử lý lỗi.

Phần code dùng để xử lý lỗi thường rất khó kiểm tra. Tốt nhất là nên có các test tự động để kiểm tra phần này, nhưng đôi khi việc này trở nên bất khả.

Một mẹo tôi hay dùng là sửa code tạm thời để kích hoạt phần xử lý lỗi. Dễ nhất là lật ngược điều kiện if lại, chẳng hạn như chuyển if error_count > 0 thành if error_count == 0.

Một ví dụ khác là giả vờ viết sai tên một column trong database để kích hoạt lỗi.

  • Sử dụng dữ liệu đầu vào ngẫu nhiên.

Một cách kiểm tra khác có thể dùng để phát hiện bug là sử dụng dữ liệu đầu vào ngẫu nhiên.

Chẳng hạn như, phần giải mã ASN.1 của giao thức H.323 hoạt động trên dữ liệu nhị phân. Bằng cách gửi các bytes ngẫu nhiên để giải mã, chúng tôi đã tìm ra rất nhiều lỗi trong phần này.

Một ví dụ khác là tạo ra những cuộc gọi thử nghiệm, với thời gian gọi, độ trễ khi trả lời, bên nào ngắt máy trước, v.v.. được tạo ra ngẫu nhiên. Những cuộc gọi này làm lộ ra một đống bug, đặt biệt là khi chúng xen vào những sự kiện xảy ra gần như cùng lúc.

  • Kiểm tra hành động không mong muốn có thật sự KHÔNG diễn ra.

Thường testing liên quan đến xem thử hành động mong muốn có xảy ra không. Nhưng lại rất dễ bỏ qua trường hợp ngược lại – kiểm tra hành động không mong muốn thật sự không diễn ra.

  • Tự làm tool.

Tôi thường tự làm các tool nhỏ để test dễ hơn.

Bằng cách bắt đầu nhỏ, và dần dần phát triển thêm tính năng cho nó, cuối cùng tôi có trong nay những công cụ rất hữu dụng. Lợi ích của việc này là tôi có những công cụ đúng như tôi mong muốn.

3. Về Debugging.

lap-trinh-vien-can-hoc-nhung-gi

Làm cách nào để khui ra bug này nhanh hơn? Tôi đã dùng đúng tool chưa? Có phải tôi đã phỏng đoán quá nhiều? Tôi có cần logging tốt hơn không?

  • Thảo luận.

Cách debug hiệu quả nhất đối với tôi là thảo luận với đồng nghiệp. Trong lúc tìm cách giải thích cho họ hiểu vấn đề gặp phải là gì, tôi cũng đồng thời hiểu sâu và rõ hơn về nó.

Thêm nữa, dù không quen thuộc với code trong câu hỏi, thường họ sẽ có cái nhìn khách quan để chỉ ra vấn đề có thể nảy sinh từ đâu.

Đây là cách cực kì hiệu quả giúp tôi giải quyết những bug khó nhằn nhất.

  • Cẩn thận đến từng tiểu tiết.

Khi việc debug ngốn quá nhiều thời gian, thì thường là do tôi đã suy đoán sai.

Cho nên, hãy chắc chắn bạn đã kiểm tra lại tất cả chi tiết thay vì mặc định mọi thứ. Thật dễ để thấy những gì bạn mong muốn thấy, hơn là những gì thật sự ở đó.

  • Thay đổi mới nhất..

Khi những thứ từng hoạt động tự dưng trục trặc, thường là do những thay đổi mới nhất gây nên.

Có trường hợp, bạn chỉ thay đổi logging, song một lỗi trong logging đã gây nên sự cố lớn hơn nhiều.

Để dễ truy tìm những sự cố kiểu này, bạn nên commit những thay đổi khác nhau trong những commit khác nhau, và ghi chú rõ ràng về việc thay đổi.

  • Tin ở người dùng.

Đôi khi người dùng report một vấn đề nào đó, ý nghĩ đầu tiên của tôi là: Không thể nào! Chắc họ nhầm lẫn chứ chuyện đó sao xảy ra được! Nhưng rồi, hóa ra họ đã report đúng.

Dĩ nhiên tôi vẫn phải kiểm tra lại để xem mọi thứ đã được thiết lập đúng chưa. Nhưng tôi đã gặp rất nhiều trường hợp kì quặc xảy ra bởi vì một thiết lập không thường gặp, một cách dùng không được dự đoán trước, hay giả định ban đầu của tôi rằng chúng phải như vậy. Và thế là chương trình chạy sai.

  • Test phần đã sửa.

Khi bug đã sửa xong thì bạn buộc phải test lại. Trước tiên, hãy chạy code mà không dùng phần đã sửa và theo dõi bug. Sau đó, sử dụng phần đã sửa và chạy lại test case.

Tuân theo những bước trên sẽ giúp bạn chắc chắn bug đó thực sự là bug, và phần đã sửa thực sự hiệu quả. Đơn giản nhưng cần thiết.

Xem thêm một số bài viết hay về Testing:

  • QA là gì? QC là gì?
  • Automation QA là gì?
  • Những kỹ năng cần thiết để trở thành Tester giỏi.

Theo bạn, lập trình viên cần học những gì từ bug? Bí quyết của bạn để biến “nỗi đau thương” fix bug thành “niềm hạnh phúc”? Hãy chia sẻ cùng cộng đồng developer Ít nhưng mà Chất của ITviec nhé!

Và đừng quên tham khảo hàng trăm việc làm Developer chất trên ITviec!

0