Khi xảy ra lỗi trong ứng dụng của bạn, Ruby đưa ra một ngoại lệ và in ra dấu vết ngăn xếp vào nhật ký. Trong bài viết này, chúng ta sẽ xem xét cách đọc dấu vết ngăn xếp và cách sử dụng nó để xác định nguồn gốc của một ngoại lệ trong ứng dụng của bạn.
Ngăn xếp cuộc gọi
Bất cứ khi nào bạn gọi một phương thức, Ruby sẽ đặt một khung ngăn xếp trên ngăn xếp cuộc gọi (hoặc "ngăn xếp thời gian chạy", nhưng thường chỉ được gọi là "ngăn xếp"). Khung ngăn xếp là một cấp phát bộ nhớ chứa các đối số của phương thức, một số không gian cho các biến bên trong và địa chỉ trả về của trình gọi.
# divide.rb
def divide(a, b)
"Dividing #{a} by #{b} gives #{a / b}."
end
puts divide(8, 4)
Khi một phương thức (divide
) gọi một phương thức khác (Fixnum#/
hoặc /
gọi tắt là), cái sau được đặt ở trên cùng của ngăn xếp, bởi vì nó cần phải thực thi trước khi cái đầu tiên có thể kết thúc. Khi gọi divide(8, 4)
trong ví dụ này, Ruby sẽ thực hiện các hành động sau theo thứ tự:
- Gọi
8./(4)
- Thu thập kết quả của phép chia (
2
), và đặt nó trong một chuỗi - Thu thập chuỗi (
"Dividing 8 by 4 gives 2"
), và in nó ra bảng điều khiển vớiputs
Dấu vết ngăn xếp
Dấu vết ngăn xếp (thường được đặt tên là "backtrace" trong Ruby, nhưng còn được gọi là "stack backtrace" và "stack traceback") là một biểu diễn có thể đọc được của con người về ngăn xếp tại một thời điểm cụ thể trong khi chạy chương trình của bạn. Dấu vết ngăn xếp cho lệnh gọi đến /
phương thức sẽ giống như thế này, cho thấy nó đã được gọi trong divide
phương thức trên dòng 2, được gọi trong <main>
phương pháp trên dòng 5.
divide.rb:2:in `/'
divide.rb:2:in `divide'
divide.rb:5:in `<main>'
Trong ví dụ trên, gọi divide
của chúng tôi phương thức với 0 là một trong các đối số của nó sẽ dẫn đến ZeroDivisionError
ngoại lệ.
# divide_by_zero.rb
def divide(a, b)
"Dividing #{a} by #{b} gives #{a / b}."
end
puts divide(8, 0)
Khi điều đó xảy ra, Ruby sẽ in ngoại lệ, cùng với dấu vết ngăn xếp, vào bảng điều khiển. Dấu vết ngăn xếp hiển thị ngăn xếp ở định dạng con người có thể đọc được, với vị trí của từng phương pháp trong mã của bạn, để giúp bạn xác định vị trí của ngoại lệ.
$ ruby divide_by_zero.rb
divide_by_zero.rb:2:in `/': divided by 0 (ZeroDivisionError)
from divide_by_zero.rb:2:in `divide'
from divide_by_zero.rb:5:in `<main>'
-
Trên dòng đầu tiên trong dấu vết ngăn xếp ở trên, chúng ta có thể thấy rằng
ZeroDivisionError
đã được nâng lên với "chia cho 0" như thông điệp của nó. Ngoài bản thân ngoại lệ, chúng ta có thể thấy rằng nó đã xảy ra trêndivide_by_zero.rb:2:in `/'
, có nghĩa là lỗi đã được phát sinh từ dòng thứ hai của tệp mẫu của chúng tôi, từ một phương thức có tên/
(làFixnum#/
, bởi vì đối số đầu tiên là Fixnum8
). -
Dòng thứ hai của dấu vết ngăn xếp hiển thị vị trí
/
phương thức đã được gọi từ. Trong trường hợp này, đó là từdivide
của chúng tôi phương pháp trên dòng 2. -
Dòng cuối cùng cho thấy rằng
divide
được gọi từ<main>
, đề cập đến ngữ cảnh ban đầu của một ứng dụng Ruby. Nói chung, nó có nghĩa là nó được gọi từ bên ngoài bất kỳ phương thức "thực" nào.
LƯU Ý :Trong Ruby 2.5, trình ghi nhật ký in ngược lại dấu vết ngăn xếp để vừa với chúng trong cửa sổ đầu cuối. Dòng cuối cùng hiển thị ngoại lệ, trước dòng ngoại lệ đã xảy ra. Các dòng phía trên là tuyến lên ngăn xếp.
Hiểu dấu vết ngăn xếp
Dấu vết ngăn xếp cung cấp cho bạn kết xuất trạng thái hiện tại của ngăn xếp cuộc gọi của bạn bất cứ khi nào một ngoại lệ được đưa ra và rất hữu ích trong việc tìm ra nơi mọi thứ đã sai.
Mặc dù dòng đầu tiên của ngăn xếp hiển thị dòng ngoại lệ đã xảy ra, nó không phải lúc nào cũng hiển thị nguồn gốc của lỗi. Trong ví dụ trên, chương trình được thực thi đúng cách, nhưng nó không thể xử lý dữ liệu được chuyển đến divide
phương pháp. Đi bộ lên dấu vết ngăn xếp dẫn đến nơi nó được gọi, là nguồn gốc của vấn đề.
Như mọi khi, chúng tôi muốn biết bạn thích bài viết này như thế nào, nếu bạn có bất kỳ câu hỏi nào về nó và những gì bạn muốn đọc tiếp theo, vì vậy hãy nhớ cho chúng tôi biết tại @AppSignal.