Tips & Trick giúp debug Javascript dễ dàng hơn

Giới thiệu

Một nhà hiền triết đã từng nói: “There are no bugs, if you don’t write any code” – “Muốn không có bug thì đừng code nữa”

Dù cho bạn có giỏi đến đâu, không có cách nào giúp bạn viết code mà không xảy ra lỗi, đã code là phải có bug. Lập trình viên dành phần lớn thời gian làm việc của họ để fix bug. Bug có thể được fix ngay khi bạn phát hiện ra nó, hoặc cũng có thể là bug được phát hiện bởi người dùng, nó có thể làm ứng dụng của bạn hoạt động không đúng, và tất nhiên, những loại lỗi này không dễ gì phát hiện, cần nhiều thời gian để tìm hiểu và fix (nếu bạn không phải là người trực tiếp viết code)

Tôi đã cần rất nhiều thời gian để có thể đọc hiểu được flow chỉ để remove một đoạn code nhỏ gây ra bug trong ứng dụng của mình. Vậy làm thế nào để có thể khoanh vùng lỗi và khắc phục nó một cách nhanh chóng và hiệu quả, từ đó có thể rút ngắn được thời gian resolve issue

Tất cả những gì chúng ta cần là một phương pháp debug “chuẩn cơm mẹ nấu”.

Các yếu tố nào khiến việc debug không hiệu quả?

Khi debug mà không có một phương pháp chính xác, có một số vấn đề tiềm ẩn mà bạn cần lưu ý:

Không đầy đủ thông tin: Việc debug chỉ dùng console.log() khiến thông tin trả về có thể không đầy đủ, dễ gây hiểu lầm và mất thời gian trong quá trình gỡ lỗi

Chỉ dùng console.log() để debug: Đồng ý rằng console.log() rất hữu ích để có thể xem được những thông tin cần thiết trong quá trình debug. Hãy tưởng tượng quá trình debug chỉ bằng console.log(), chúng ta cần phải khoanh vùng đoạn code có thể xảy ra lỗi ⇒ đặt console.log ⇒ compile lại code ⇒ trở ra trình duyệt ⇒ mở inspect và xem thông tin được log, đây là một quá trình tốn thời gian. Hiện tại, có nhiều công cụ có thể giúp chúng ta debug nhanh chóng và thuận tiện hơn

Không hiểu flow code, fix mà không biết mình fix gì: Một điều quan trọng để fix bug một cách hiệu quả là hiểu bản chất, hiểu function mà những dòng code đang thực thi. Nếu bạn không hiểu flow code, việc sửa 1 lỗi nhỏ có thể gây ra nhiều lỗi khác.

Cần phải làm gì khi đối mặt với bug – debug?

Bản chất của việc debug:

  • Xác định nguồn gốc của lỗi (Khoanh vùng và thu hẹp vị trí có khả năng gây ra lỗi, đặc biệt với các dự án có khối lượng code base lớn)
  • Hiểu lý do tại sao lỗi xảy ra (Điều tra bản chất sâu xa của các yếu tố gây ra lỗi)

Để giải quyết hai điểm trên, việc hiểu rõ về luồng code và các tính năng liên quan của mã là cần thiết để tránh tạo ra các lỗi liên quan. Hơn nữa, để làm cho quá trình sửa lỗi hiệu quả hơn, việc có một phương pháp gỡ lỗi đúng đắn là cần thiết. Dưới đây là một số công cụ có thể giúp đơn giản hóa quá trình gỡ lỗi.

Tips and Tricks debug Javascript hiệu quả

Debugger

“debugger” statement gọi bất kỳ chức năng debug có sẵn nào (của trình duyệt hoặc IDE), chẳng hạn như đặt breakpoints. Nếu không có chức năng sửa lỗi, câu lệnh này không có hiệu lực – tham khảo thêm source

Breakpoints

Như một phương pháp thay thế cho câu lệnh debugger, bạn cũng có thể thêm các điểm dừng (breakpoints) trong Chrome DevTools một cách thủ công, bằng cách sử dụng chức năng breakpoints tích hợp sẵn.

Bạn có quyền truy cập vào các loại breakpoints sau đây:

  • line-of-code breakpoints
  • conditional line-of-code breakpoints
  • logpoints
  • DOM breakpoints
  • XHR breakpoints
  • event listener breakpoints
  • breakpoints set at caught and uncaught exceptions
  • function breakpoints

Bạn có thể tham khảo thêm về breakpoint tại đây.

Hiển thị objects dưới dạng table

Đôi khi, bạn có một tập hợp phức tạp các đối tượng. Bạn có thể sử dụng console.log, hoặc sử dụng công cụ console.table để làm điều đó. Nó giúp bạn dễ dàng nhìn thấy những gì bạn đang làm việc!

const animals = [
    { animal: 'Horse', name: 'Henry', age: 43 },
    { animal: 'Dog', name: 'Fred', age: 13 },
    { animal: 'Cat', name: 'Frodo', age: 18 }
];
 
console.table(animals);

Sẽ cho ra:

Tìm DOM element nhanh chóng

Đánh dấu một phần tử DOM trong bảng elements và sử dụng nó trong console. Chrome Inspector lưu lại năm phần tử cuối cùng trong lịch sử của nó để phần tử cuối cùng được đánh dấu hiển thị với $0, phần tử được đánh dấu trước đó là $1 và cứ như vậy. Nếu bạn đánh dấu các phần tử theo thứ tự ‘item-4’, ‘item-3’, ‘item-2’, ‘item-1’, ‘item-0’ thì bạn có thể truy cập các nút DOM như sau trong console:

Đo thời gian thực thi các vòng lặp bằng cách sử dụng console.time() và console.timeEnd()

Đôi khi, việc biết chính xác thời gian mất để thực thi một công việc là rất hữu ích, đặc biệt khi gỡ lỗi các vòng lặp và quyết định sử dụng phương pháp nào để tối ưu hiệu suất cho công việc bạn đang xử lý. Bạn có thể thiết lập timer bằng cách gán một nhãn cho phương thức. Hãy xem cách nó hoạt động:

console.time('Timer1');
let items = [];
for(let i = 0; i < 100000; i++){
   items.push({index: i});
}
console.timeEnd('Timer1');

Sẽ cho ra:

Lấy thông tin về Stack Trace cho một hàm

Bạn sẽ có nhiều hiển thị và kích hoạt sự kiện, vì vậy cuối cùng bạn sẽ gặp tình huống muốn biết cái gì đã gây ra một cuộc gọi hàm cụ thể. Vì JavaScript không phải là một ngôn ngữ có cấu trúc rõ ràng, đôi khi khó có cái nhìn tổng quan về điều gì đã xảy ra và khi nào.

let car;
let func1 = function() {
	func2();
}

let func2 = function() {
	func4();
}
let func3 = function() {
}

let func4 = function() {
	car = new Car();
	car.funcX();
}
let Car = function() {
	this.brand = 'volvo';
	this.color = 'red';
	this.funcX = function() {
		this.funcY();
	}

	this.funcY = function() {
		this.funcZ();
	}

	this.funcZ = function() {
		console.trace('trace car')
	}
}
func1();

Sẽ cho ra:

Ví dụ, khi bạn chạy đoạn mã “func1()”, nó sẽ gọi “func2()”, sau đó “func2()” sẽ gọi “func4()”, và cuối cùng “func4()” sẽ gọi “funcZ()”. Khi “funcZ()” được gọi, một dòng thông báo sẽ xuất hiện trên console, cho bạn biết rằng “trace car” đã được gọi từ vị trí đó trong stack trace.

Việc sử dụng console.trace (hoặc trace trong console) giúp bạn xác định được nguyên nhân của một cuộc gọi hàm cụ thể trong quá trình chạy mã JavaScript của bạn. Nó cung cấp một cái nhìn tổng quan về những gì đã xảy ra và khi nào xảy ra. Điều này rất hữu ích để gỡ lỗi và theo dõi luồng thực thi của mã của bạn.

Tìm nhanh một chức năng để debug

Hãy tưởng tượng bạn muốn đặt một điểm dừng (breakpoint) trong một function.

Có thể làm theo cách sau:

Tìm function trong inspect table và thêm một điểm dừng (breakpoint).

Thêm debugger statement trong code

Trong cả hai giải pháp này, bạn phải điều hướng thủ công trong các tệp của bạn để tìm dòng cụ thể bạn muốn gỡ lỗi. Điều ít phổ biến hơn có lẽ là sử dụng console. Sử dụng debug(funcName) trong console và script sẽ dừng lại khi nó đạt đến hàm mà bạn đã truyền vào. Điều này nhanh chóng, nhưng nhược điểm là nó không hoạt động với các function hoặc function. Nếu không có điều kiện đó, đây có lẽ là cách nhanh nhất để tìm một hàm để gỡ lỗi. (Lưu ý: có một hàm được gọi là console.debug nhưng không phải là cùng một chức năng, mặc dù tên giống nhau.)

debug(car.funcY) trong cửa sổ console và script sẽ dừng lại ở chế độ debug khi nó nhận một cuộc gọi hàm đến car.funcY.

Tìm những điểm quan trọng trong quá trình gỡ lỗi phức tạp

Trong quá trình gỡ lỗi phức tạp hơn, đôi khi chúng ta muốn xuất ra nhiều dòng thông tin. Một điều bạn có thể làm để giữ cho đầu ra của bạn có cấu trúc tốt hơn là sử dụng nhiều hàm console, ví dụ: console.log, console.debug, console.warn, console.info, console.error …hoặc sử dụng console.group. Sau đó, bạn có thể lọc chúng trong bảng điều khiển của mình. Đôi khi điều này không phải là điều bạn thực sự muốn khi bạn cần gỡ lỗi JavaScript. Bạn có thể sáng tạo và tạo kiểu cho các thông báo của mình nếu bạn muốn. Sử dụng CSS và tạo ra các thông báo console có cấu trúc riêng khi bạn muốn gỡ lỗi JavaScript.

console.todo = function(msg) {
    console.log('%c%s %s %s', 'color: yellow; background-color: black;', '--', msg, '--');
}

console.important = function(msg) {
    console.log('%c%s %s %s', 'color: brown; font-weight: bold; text-decoration: underline;', 
'--', msg, '--');
}

console.todo("This is something that needs to be fixed");
console.important('This is an important message');

Sẽ cho ra:

Theo dõi các cuộc gọi hàm cụ thể và đối số

Trong bảng điều khiển Chrome, bạn có thể theo dõi các hàm cụ thể. Mỗi khi hàm được gọi, nó sẽ được ghi lại cùng với các giá trị được truyền vào.

let demo = function(x, y, z) {
    console.log(x, y, z)
};

const button = document.querySelector('.button')
button.addEventListener('click', () => demo(1,2,3))

Đây là một cách tuyệt vời để xem các đối số được truyền vào một hàm. Lý tưởng nhất, console có thể cho biết số lượng đối số cần truyền vào hàm. Trong ví dụ trên, demo mong đợi ba đối số, nhưng chỉ có hai được truyền vào. Nếu điều đó không được xử lý trong mã, có thể dẫn đến lỗi tiềm ẩn.

Truy cập nhanh các element trong cửa sổ console

Một cách nhanh hơn để thực hiện querySelector trong console là sử dụng ký hiệu đô la ($). $(‘css-selector’) sẽ trả về phần tử đầu tiên khớp với CSS selector. $$(‘css-selector’) sẽ trả về tất cả các phần tử khớp. Nếu bạn sử dụng một phần tử nhiều lần, đáng giá để lưu nó thành một biến.

Dừng lại khi có thay đổi trong node (element)

DOM có thể là một điều thú vị. Đôi khi có những thay đổi xảy ra mà bạn không biết tại sao. Tuy nhiên, khi bạn cần gỡ lỗi JavaScript, Chrome cho phép bạn tạm dừng khi một phần tử DOM thay đổi. Bạn còn có thể theo dõi các thuộc tính của nó. Trong Chrome Inspector, nhấp chuột phải vào phần tử và chọn “Break on…” để sử dụng:

Kết luận

Gỡ lỗi JavaScript có thể là một nhiệm vụ đầy thách thức, nhưng bằng cách sử dụng các công cụ và kỹ thuật phù hợp, bạn có thể làm quá trình này trở nên hiệu quả và hiệu quả hơn. Khía cạnh quan trọng nhất là hiểu rõ luồng code của dự án để tránh tạo ra các lỗi bổ sung.

TuanDV

Comments

Let’s make a great impact together

Be a part of BraveBits to unlock your full potential and be proud of the impact you make.