Cho dù bạn là người mới bắt đầu hay một lập trình viên nhiều kinh nghiệm, bạn đều có thể tìm thấy lỗi trong code của mình. Tất cả chúng ta đều có lỗi trong các ứng dụng của mình vì rất nhiều lý do: giao tiếp không hiệu quả, chương trình phức tạp, áp lực thời gian, thay đổi phạm vi, lỗi thiết kế, do kỹ năng, thiếu chương trình quản lý phiên bản…. Nhưng lý do phổ biến nhất là những sai lầm của con người trong thiết kế và lập trình. Việc tránh hoàn toàn không có một lỗi nào khi lập trình là gần như không thể. Tuy nhiên, việc cải thiện kỹ năng debug để giảm tối thiểu lỗi trong chương trình là điều khả thi. Vậy chúng ta, những lập trình viên có thể làm gì?
Làm thế nào chúng ta có thể đối phó với lỗi?
Có ba cách chính để đối phó với lỗi:
- Ngăn chặn (pre-debugging): giảm thiểu các lỗi trước khi chúng được tạo ra
- Gỡ lỗi: xác định, sửa chữa và loại bỏ lỗi khi bạn tìm thấy chúng
- Hậu gỡ lỗi: sẽ có lỗi không mong muốn hoặc không xác định
Chúng ta hãy xem xét từng chi tiết một.
Prebugging là gì?
Nhà khoa học máy tính quá cố người Hà Lan, Edsger W. Dijkstra đã cho rằng:
“Nếu gỡ lỗi là quá trình loại bỏ lỗi, thì lập trình phải là quá trình đưa chúng vào. (If debugging is the process of removing bugs, then programming must be the process of putting them in.)”
Nếu chúng ta đưa lỗi vào một chương trình thông qua lập trình, điều đó có nghĩa là chúng ta cần phải giảm số lượng lỗi mà chúng ta đưa vào. Ta có thể gọi quá trình này là “Prebugging”. Theo định nghĩa trong Từ điển Oxford: “[Gỡ lỗi là] quá trình xác định và loại bỏ các lỗi khỏi phần cứng hoặc phần mềm của máy tính.” Vậy chúng ta cần phải làm gì?
Một số cách chúng ta có thể làm để giảm thiểu lỗi khi lập trình:
- Viết mô tả chương trình.
- Tìm hiểu để thực sự nắm rõ các công cụ bạn sử dụng.
- Học cách gõ chính xác.
- Tự làm quen với các thông báo lỗi và các giải pháp có thể xảy ra của chúng.
- Luôn đảm bảo rằng bạn có các thiết lập đúng cho hầu hết các công cụ bạn sử dụng.
Và còn nhiều nữa! Chúng ta hãy cùng đi vào chi tiết:
Tìm hiểu về các công cụ bạn thường sử dụng
Điều quan trọng là phải tìm hiểu cơ bản của bất kỳ công cụ nào bạn thường sử dụng, vì điều này giúp bạn giảm số lượng lỗi bạn tạo ra trong khi viết code. Không có cách nào để tránh hoàn toàn việc tạo ra lỗi, nhưng bạn có thể tránh tạo ra một số lỗi nếu kiến thức cơ bản về các công cụ bạn sử dụng là đủ tốt.
Ví dụ, nhiều người dùng JavaScript không thể nhớ splice()
trả về là gì. Và một số người không thể nhớ sự khác biệt giữa phương thức mảng map()
và forEach()
. Nếu bạn không phải là người dùng JavaScript, chỉ cần chọn một phương pháp hoặc hàm tích hợp sẵn từ ngôn ngữ bạn sử dụng và tự hỏi: Những loại đối số nào được dùng? Điều này có nghĩa là gì? Điều gì xảy ra nếu một đối số không hợp lệ được cung cấp? Tự hỏi bản thân những câu hỏi ở trên về từng phần tích hợp của bất kỳ công cụ nào bạn sử dụng thường xuyên có thể giúp bạn tìm hiểu và luôn cập nhật. Đó là cách luôn cập nhật những kiến thức cơ bản về các công cụ bạn thường sử dụng, đặc biệt nếu bạn không có nhiều thời gian.
Lập kế hoạch trước khi lập trình
Lập trình có thể giống như một môn thể thao thử và sai khi bạn thực hiện nó cho đến khi bạn làm đúng. Nhiều nhà phát triển phần mềm mới bắt đầu không thực sự hiểu các chương trình họ đang làm việc và một số người trong số họ không thực sự cố gắng hiểu các thông báo lỗi trước khi tìm kiếm trên google. Mọi người dường như cảm thấy rằng lập trình luôn là về “Code, code, code, tìm kiếm, gỡ lỗi”. Nhưng cần phải thực sự hiểu những gì bạn đang làm để có thể nhanh chóng viết ra:
- Những gì chúng ta lấy làm đầu vào (input) cũng như cấu trúc và tính năng của những đầu vào đó.
- Những gì chúng ta sẽ làm với đầu vào.
- Những gì chúng ta mong đợi sẽ được trả về hoặc làm được liên quan đến các yếu tố đầu vào hoặc những thứ khác.
- Chúng ta sẽ làm gì nếu không có được các đầu vào như dự kiến.
Tóm lại, lập kế hoạch đầu vào, quy trình và đầu ra của một chức năng hoặc chương trình không chỉ giúp bạn giảm lỗi mà còn giúp bạn viết các bài kiểm thử hiệu quả.
Tự làm quen với các thông báo lỗi phổ biến
Thường rất dễ sửa một lỗi hoặc một lỗi nếu bạn đã làm quen với lỗi đó. Đó là lý do tại sao điều quan trọng là phải dành thời gian để nghiên cứu một số lỗi phổ biến và học cách sửa chúng. Bây giờ chúng ta hãy nói về một số lỗi phổ biến:
1. Syntax Errors
Mỗi ngôn ngữ lập trình đều có các quy tắc riêng và các lập trình viên có thể vi phạm các quy tắc đó. Khi đó chúng sẽ đưa ra lỗi khi nào các quy tắc đó bị vi phạm.
Ví dụ, hãy tưởng tượng rằng bạn bỏ qua dấu ngoặc đơn của một hàm hoặc phương thức như sau:
function {}
Một lỗi sẽ xuất hiện.
Việc làm quen với thông báo lỗi của một lỗi cú pháp và cách khắc phục nó sẽ giúp bạn có lợi ích trong khi gỡ lỗi nó. Hầu hết các lỗi cú pháp luôn đề cập đến một số từ khóa giúp bạn tìm ra phần mã của bạn bị lỗi.
let school = {
name: "Harvard",
location: "Heaven On Earth", admit: function() { return "weeew! You are admitted" }
}
console.log(school.names); // undefined
“Không xác định (undefined)” được trả về cho chúng ta biết liệu đối tượng hoặc thuộc tính chúng ta đang truy cập có khả dụng hay không. Chúng ta có thể tìm ra vấn đề ở đâu nếu chú ý đến thông báo lỗi. Bây giờ, hãy lấy ví dụ xa hơn một chút.
console.log(school.locations.address); // Uncaught TypeError: Cannot read property 'address' of undefined.
Nếu chúng ta chú ý đến thông báo lỗi, chúng ta có thể dễ dàng tìm ra lỗi ở đâu.
Từ thông báo lỗi ở trên, “Không thể đọc thuộc tính ‘address’ của undefined (Cannot read property ‘address’ of undefined)” có nghĩa là địa chỉ đó là một thuộc tính và một thuộc tính được biết là nằm trong một đối tượng (trong JavaScript). Nhưng trong trường hợp này, đối tượng được cho là “không xác định”.
Càng viết nhiều code, bạn càng tránh được lỗi cú pháp tốt hơn. Bạn cũng có thể chỉ cần sử dụng trình chỉnh sửa mã, linters hoặc IDE để đánh dấu các lỗi cú pháp. Sử dụng những công cụ này có thể giúp bạn rất nhiều.
Bạn có thể kiểm tra các code linters này để xem cái nào hoạt động tốt nhất cho trường hợp sử dụng của bạn:
ESLint cho JavaScript
PyLint cho Python
Checkstyle cho Java
PHP_CodeSniffer cho PHP
Ngoài ra, hầu hết các trình soạn thảo mã phổ biến như VSCode đều có thể được cấu hình để sử dụng các đoạn mã ở trên.
2. Logic/Semantic Errors
Các lỗi logic rất khó xử lý vì chúng luôn có vẻ như không có lỗi, nhưng bạn vẫn không nhận được kết quả như mong đợi.
Ví dụ: một cách đơn giản để xác nhận loại lỗi này là kiểm tra code bên dưới trong bảng điều khiển của trình duyệt.
prompt("enter number") + 3;
Bạn có thể mong đợi một số như một đầu ra, nhưng nó sẽ trả về một chuỗi. Tóm lại, bạn sẽ không nhận được kết quả như mong đợi.
Lập kế hoạch trước khi viết code và hiểu những điều cơ bản về ngôn ngữ lập trình bạn sử dụng có thể giúp bạn đối phó với các lỗi logic – miễn là bạn hiểu các yêu cầu chương trình đưa ra cho bạn.
3. Compilation Errors
Chương trình của bạn có thể không biên dịch vì bạn có thể đã vi phạm một số quy tắc mà trình biên dịch mong muốn bạn tuân theo. Vì vậy, chương trình bạn đang làm việc có thể không biên dịch.
Ví dụ, viết một chuỗi mà không có dấu ngoặc kép thông thường, như trong const name = Ayobami
sẽ dẫn đến lỗi biên dịch vì một chuỗi phải được trích dẫn. Vì vậy, code sẽ không biên dịch.
Điều này tương tự với lỗi cú pháp, và bạn càng viết nhiều mã, bạn càng xử lý tốt hơn các lỗi biên dịch. Bạn có thể hiệu quả hơn và giảm những lỗi này bằng cách biên dịch hoặc kiểm tra mã của mình thường xuyên.
4. Resource Errors
Đôi khi, chương trình của bạn có thể vượt quá giới hạn bộ nhớ hoặc sử dụng hết tài nguyên có sẵn. Điều đó có thể khiến ứng dụng của bạn ngừng hoạt động hoặc hoạt động sai.
Đoạn mã dưới đây là một ví dụ trong thế giới thực về đoạn mã dẫn đến lỗi tài nguyên.
function factorial(num) {
var result = 1;
for(var i = num; i > 0; i--){
result = num * factorial(num-1);
}
return result;
}
factorial(5);
factorial(10);
factorial(20);
factorial(0);
Nguồn (Freecodecamp)
Hàm factorial()
bị treo hoặc làm chậm trình duyệt vì không gian ngăn xếp, tức là bộ nhớ mà trình duyệt phân bổ cho chuỗi lệnh gọi hàm, đã được sử dụng hết. Trong trường hợp này, lỗi là lỗi tài nguyên vì nó xảy ra do sử dụng hết bộ nhớ được cấp phát (tài nguyên).
5. Interface Errors
Đôi khi chúng ta thiết kế các API chương trình để sử dụng theo những cách nhất định nhưng người dùng sử dụng các chương trình theo cách khác nhau và gây ra lỗi. Những lỗi như vậy được gọi là lỗi giao diện.
Ví dụ, giả sử rằng phương thức go(string)
mong đợi một chuỗi nhưng thay vào đó chúng ta gọi nó bằng một số. Điều đó sẽ dẫn đến lỗi nếu người tạo ra chương trình không mong đợi và quản lý cách chương trình sẽ phản hồi trong trường hợp như vậy.
Hầu hết mọi thứ trong phần mềm đều tuân theo các tiêu chuẩn. Nếu các tiêu chuẩn đã xác định của bạn không được tuân thủ, bạn cần cung cấp cho người dùng của mình thông báo lỗi hoặc hướng dẫn để giúp họ tìm ra họ đang sử dụng ứng dụng sai.
Việc ghi lại các API của bạn có thể giúp ích rất nhiều trong trường hợp này.
Đảm bảo các thiết lập của bạn phù hợp với các công cụ
Điều quan trọng là phải có một thiết lập phù hợp với các công cụ của bạn. Đôi khi, hệ điều hành của bạn có thể không tương thích với các ứng dụng của bạn – có thể vì nó yêu cầu phiên bản hệ điều hành mới hơn hoặc nó yêu cầu một phần mềm nhất định.
Ví dụ: WampServer có thể không chạy đúng trên Windows OS nếu thiếu một số Microsoft VC runtime. Những điều tương tự cũng có thể xảy ra với Linux và macOS.
Bạn chỉ cần chắc chắn rằng thiết lập của mình phù hợp với bất kỳ công việc gì bạn làm.
Hãy xác định về các chức năng của chương trình của bạn
“Trong toán học, khoa học máy tính và vật lý, một hệ thống xác định là một hệ thống mà tính ngẫu nhiên không liên quan đến sự phát triển của các trạng thái tương lai của hệ thống.
Do đó, một mô hình xác định sẽ luôn tạo ra cùng một đầu ra từ một điều kiện bắt đầu hoặc trạng thái ban đầu nhất định. “ Nguồn
Câu hỏi đặt ra là, làm thế nào để chúng ta tạo ra một chương trình xác định? Bạn phải chắc chắn về loại dữ liệu được chấp nhận trong chương trình của bạn và từ chối bất kỳ dữ liệu nào không phù hợp.
Tóm lại, bạn cần lấy dữ liệu dự kiến và từ chối dữ liệu không mong muốn hoặc thông báo cho người dùng của bạn về dữ liệu dự kiến.
Đừng sử dụng nếu bạn không hiểu nó
Một trong những cách tốt nhất để giảm thiểu việc tạo ra lỗi là chỉ sử dụng các cách tiếp cận, phương pháp và lớp mà bạn hiểu. Nếu bạn phải sử dụng bất kỳ cách tiếp cận hoặc phong cách nào mà bạn không hiểu, hãy nghiên cứu nó và chắc chắn về những gì bạn sắp làm trước khi thực hiện.
Thật dễ dàng đưa ra các lỗi không cần thiết cho ứng dụng của bạn bất cứ khi nào bạn sử dụng những thứ bạn không hiểu.
Học cách gõ chính xác
Gõ chính xác bị đánh giá thấp, bởi vì lập trình thiên về tư duy hơn là nhập. Nhưng chính xác trong khi gõ có thể giúp bạn giảm một số lỗi cú pháp, lỗi nhập hoặc lỗi chính tả.
Nhiều lỗi lập trình là do lỗi đánh máy đơn giản. Khả năng gõ chính xác giúp bạn có lợi thế trong việc giảm thiểu lỗi.
Xem các lập trình viên đồng nghiệp trong khi gỡ lỗi
Một cách thú vị khác để cải thiện kỹ năng gỡ lỗi của bạn là xem các nhà phát triển đồng nghiệp trong khi họ đang gỡ lỗi. Nó giúp xem các phương pháp gỡ lỗi khác nhau, đặc biệt là thông qua ống kính của họ.
Sẽ luôn có những công cụ hoặc cách tiếp cận mà chúng ta không biết hoặc không sử dụng để gỡ lỗi. Quan sát những người khác cho chúng ta cơ hội khám phá những công cụ hoặc cách tiếp cận mà chúng ta có thể không biết.
Hoặc ngay cả khi bạn biết về những cách tiếp cận khác nhau đó, bạn có thể không biết tại sao hoặc cách sử dụng chúng. Theo dõi những người khác có thể ảnh hưởng đến chúng ta để xem lại các phương pháp và công cụ này mà cuối cùng có thể cải thiện kỹ năng gỡ lỗi của chúng ta.
Debug là gì?
Debug (gỡ lỗi) là cốt lõi của lập trình, vì nó chiếm phần trăm thời gian lớn nhất của bạn trong khi viết mã. Có ba giai đoạn chính liên quan đến gỡ lỗi:
- Tìm lỗi.
- Phân tích và hiểu lý do tại sao lỗi xảy ra.
- Sửa chữa hoặc loại bỏ lỗi.
Cách tìm lỗi
Tìm lỗi bắt đầu bằng việc hiểu các thông báo lỗi mà bạn thấy. Không cần phải nói rằng một thông báo lỗi là một con trỏ dẫn đến một lỗi. Nếu bạn hiểu thông báo lỗi, bạn có thể theo dõi chính xác vị trí của lỗi. Nhưng một số lỗi có thể gây mệt mỏi vì chúng có thể không có thông báo lỗi rõ ràng. Chúng ta chỉ có thể không nhận được một kết quả mong đợi.
Để tìm lỗi, bạn cần:
- Hãy rõ ràng về kỳ vọng của bạn.
- Kiểm tra kết quả bạn nhận được.
- So sánh kỳ vọng của bạn và kết quả thực tế để xem điều gì còn thiếu.
Bạn có thể sử dụng trình gỡ lỗi hoặc các công cụ hữu ích khác để tìm các lỗi đó một cách nhanh chóng. Sau đó, bạn có thể kiểm tra các phần khác nhau của mã so với các giả định của mình và thực hiện thử và sai để tìm ra lỗi.
Làm thế nào để hiểu tại sao lỗi xảy ra
Sau khi tìm ra lỗi, bạn cần tìm ra lý do tại sao code lại hoạt động theo cách mà nó thực hiện. Làm điều này giúp bạn xây dựng một hệ thống hiệu quả. Nhiều lập trình viên sẽ chỉ google và sử dụng câu trả lời họ nhận được trực tiếp từ StackOverflow. Điều đó tốt trong một số trường hợp nhất định, nhưng tốt hơn là bạn nên hiểu nguyên nhân của lỗi và tại sao giải pháp hoạt động. Hiểu nguyên nhân gây ra lỗi là một bước quan trọng trên con đường sửa chữa hoặc loại bỏ lỗi.
Cách sửa hoặc loại bỏ lỗi
Tuy nhiên, có những lúc sự hiểu biết của chúng ta không mang lại giải pháp nào cho dù chúng ta có cố gắng đến đâu. Thay vì mất thời gian, bạn có thể thông báo lỗi trên Google hoặc bất cứ điều gì bạn cảm thấy phù hợp. Bạn cũng có thể hỏi một người khác vì những người khác có xu hướng nhìn mọi thứ theo cách khác. Họ trung lập và sự trung lập đó thực sự giúp sửa một số lỗi.
Vậy thì, hãy tra Google, đặt câu hỏi trên StackOverflow, Twitter hoặc bất cứ nơi nào bạn kết nối với các nhà phát triển khác. Không có gì phải ngại cả. Tất cả chúng ta đều làm những điều đó hàng triệu lần. Khắc phục một lỗi đáng lo ngại luôn mang lại sự phấn khích tột độ. Nhưng đừng quá hứng thú vì việc sửa một lỗi có thể gây ra một lỗi khác. Vì vậy, trước tiên hãy chắc chắn rằng bạn đã không tạo ra một vấn đề khác cho chương trình. Đó là lý do tại sao các bài kiểm thử tự động (automated tests) lại quan trọng.
Post-debugging là gì?
“Post-debugging” hay hậu gỡ lỗi là dự đoán các lỗi không mong muốn trong các chương trình bạn đã viết. Nó đề cập đến tất cả các cơ chế bạn có thể sử dụng để đảm bảo rằng các lỗi không xác định được dễ dàng theo dõi hoặc quản lý trước khi chúng gây hại cho hệ thống hoặc công ty. Câu hỏi bây giờ là làm thế nào để bạn làm điều đó? Câu trả lời là, sử dụng một hệ thống theo dõi lỗi. Bạn nên có một hệ thống theo dõi lỗi để bạn có thể dễ dàng phát hiện ra các lỗi khi chúng xuất hiện sau khi đưa ứng dụng của bạn vào phiên bản chính thức. Có rất nhiều công cụ theo dõi lỗi trên mạng và bạn dễ dàng tìm trên Google. Dưới đây là một số công cụ mà bạn có thể tham khảo:
Có rất nhiều công cụ theo dõi lỗi trên mạng, bạn sẽ phải nghiên cứu để tìm ra thứ tốt nhất cho mình.
Kết luận
Debug là một kỹ năng chính mà tất cả các lập trình viên, nhà phát triển phần mềm phải trau dồi. Nó là cốt lõi của việc lập trình, và nếu bạn làm tốt, nó có thể giúp bạn trở thành một lập trình viên tốt hơn.
Để thành công trong việc debug, bạn phải tìm hiểu càng nhiều càng tốt về các phương pháp gỡ lỗi khác nhau như đã thảo luận ở đây trong bài viết này. Đã đến lúc trở thành một nhà phát triển phần mềm tốt hơn và việc gỡ lỗi có thể giúp ích cho bạn. Bạn chỉ cần áp dụng mọi thứ vào thực tế và kỹ năng phát triển phần mềm của bạn sẽ được cải thiện.
Bài gốc của tác giả Ayobami đăng trên FreeCodeCamp