12/08/2018, 16:58

Ruby Exception Handling: SyntaxError

Ở bài viết này sẽ tìm hiểu sâu về SyntaxError trong Ruby on Rails. SyntaxError là một lớp con được kế thừa từ lớp ScriptError và nó bật lên bất cứ lúc nào gọi đến Ruby để cố bắt lỗi cú pháp mã không hợp lệ. Trong bài viết này chúng ta sẽ tìm hiểu về lớp SyntaxError để nhìn thấy chính xác vị trí của ...

Ở bài viết này sẽ tìm hiểu sâu về SyntaxError trong Ruby on Rails. SyntaxError là một lớp con được kế thừa từ lớp ScriptError và nó bật lên bất cứ lúc nào gọi đến Ruby để cố bắt lỗi cú pháp mã không hợp lệ. Trong bài viết này chúng ta sẽ tìm hiểu về lớp SyntaxError để nhìn thấy chính xác vị trí của nó ở đâu, thứ tự bắt lỗi trong hệ thống phân lớp Exception của Ruby, làm thế nào để đối phó với SyntaxErrors và cách thực hiện tốt nhất để tránh trường hợp ngoại lệ này hoàn toàn.

The Technical Rundown

  • Tất cả các Ruby exceptions đều được kế thừa từ lớp Exception hoặc là một lớp con trong đó.
  • ScriptError được kế thừa từ lớp Exception.
  • SyntaxError được kế thừa từ lớp ScriptError.

When Should You Use It?

Có lẽ ,việc đầu tiên phải cân nhắc khi kiểm tra SyntaxErrors đó là, bản thân nó không thể rescue trực tiếp trong một đoạn mã đơn. Ví dụ: Hãy thử 1 đoạn code đơn giản, mà có dòng code lỗi là 1=2. Ở đây, Ruby sẽ mong chờ 1 trong trường hợp này là 1 object và được gán giá trị là 2, chứ không phải là 1 Fixnum object (trong khi 1 lại là 1 fixnum object) nên code đó sẽ bị sai và phát sinh Syntax Error.

def print_exception(exception, explicit)
    puts "[#{explicit ? 'EXPLICIT' : 'INEXPLICIT'}] #{exception.class}: #{exception.message}"
    puts exception.backtrace.join("
")
end
 
begin
    1=2
rescue SyntaxError => e
    print_exception(e, true)
end

Output:

code.rb:8: syntax error, unexpected '=', expecting keyword_end
    1=2
      ^

Có thể nhận thấy, vấn đề ở đây là mặc dù chúng ta đã thêm mệnh đề giải cứu của chúng ta và rõ ràng lấy bất kỳ SyntaxErrors nào - và đầu ra cho thấy một SyntaxError thực sự được tạo ra - mệnh đề giải cứu của chúng ta không bao giờ thực sự cháy.

Lý do: Bởi Ruby gặp SyntaxError trong quá trình phân tích cú pháp trong begin, đó là nơi Ruby sẽ mã hóa code thành một cây phân tích cú pháp được sử dụng để (perform actual execution). Nếu bạn muốn tìm hiểu chi tiết về cách Ruby chuyển mã của bạn thành một cây phân tích cú pháp như thế nào bạn, bạn có thể áp dụng và tìm hiểu qua gem ParseTree. Như vậy, điều này có nghĩa bạn gần như không thể thực hiện rescue SyntaxError. Nhưng vẫn sẽ có một số trường hợp ngoại lệ đối với quy tắc này. SyntaxError có thể xảy ra ở nhiều đoạn mã, SyntaxError có thể rescue được khi các đoạn mã lỗi SyntaxError năm trong các tập tin bên ngoài được required bởi tập tin thực hiện lệnh rescue. Ví dụ: Lấy đoạng mã 1=2 và di chuyển nó vào một tệp riền invalid.rb

# invalid.rb
1=2

Như vậy, thay vì trực tiếp thực thi đoạn mã lỗi syntax trong khối begin trước đó, chúng ta sẽ thay thế chúng bằng cách sử dụng phương thức require_relative() để đưa đoạn mã không hợp lệ vào chuỗi thực hiện:

# 2
def print_exception(exception, explicit)
    puts "[#{explicit ? 'EXPLICIT' : 'INEXPLICIT'}] #{exception.class}: #{exception.message}"
    puts exception.backtrace.join("
")
end

begin
    require_relative "invalid"
rescue SyntaxError => e
    print_exception(e, true)
end

Trong đoạn mã này, Ruby đã trì hoãn việc xử lý mã không hợp lệ, do đó rescue được thực hiện và phát hiện SyntaxError như thế hàm print_exception() đã thực hiện đúng chức năng như mong muốn. Mặc dù lỗi thực tế là giống nhau, đầu ra lại khác nhau kể cả đã sử dụng rescue để bắt lỗi. Output:

[EXPLICIT] SyntaxError: invalid.rb:2: syntax error, unexpected '=', expecting end-of-input

Bạn cũng có thể rescue SyntaxErrors được tạo ra từ bên trong một phương thức eval():

def print_exception(exception, explicit)
    puts "[#{explicit ? 'EXPLICIT' : 'INEXPLICIT'}] #{exception.class}: #{exception.message}"
    puts exception.backtrace.join("
")
end

begin
    eval("1=2")
rescue SyntaxError => e
    print_exception(e, true)
end

Output:

[EXPLICIT] SyntaxError: (eval):1: syntax error, unexpected '=', expecting end-of-input

Cũng giống như các subclass Exception khác mà không kế thừ trực tiếp từ StandardError, bạn phải chỉ định rõ tên lớp trong mệnh đề rescue để nắm bắt được SyntaxErrors:

def print_exception(exception, explicit)
    puts "[#{explicit ? 'EXPLICIT' : 'INEXPLICIT'}] #{exception.class}: #{exception.message}"
    puts exception.backtrace.join("
")
end
 
begin
    eval("1=2")
rescue => e
    print_exception(e, false)
end

Khi đó SyntaxError vẫn xuất hiện, nhưng nó sẽ không được rescue ghi lại và khối rescue và hàm print_exception() sẽ không hoạt động như mong muốn. Ouput:

code.rb:44:in `eval': (eval):1: syntax error, unexpected '=', expecting end-of-input (SyntaxError)
        from code.rb:44:in `<main>'

0