+1

Ruby Metaprogramming - eval

Thuật ngữ Metaprogramming đề cập đến khả năng xây dựng một chương trình có khả năng tạo ra, thực thi một chương trình khác hoặc thực thi chính nó. Việc sử dụng metaprogramming không chỉ bó gọn trong một ngôn ngữ nhất định, mà ta có thể sử dụng nhiều ngôn ngữ khác nhau trong một chương trình. Trong một số trường hợp, metaprogramming còn được hiểu là khả năng thay đổi chức năng của chính bản thân chương trình đó. Trong loạt bài về Metaprogramming, mình sẽ giới thiệu một số tính năng tuyệt vời của metaprogramming trong ngôn ngữ Ruby. Bài đầu tiên này chúng ta sẽ nói về eval.

1- Giới thiệu về eval

Giống như nhiều ngôn ngữ lập trình khác như php, js,... Ruby cũng là một script language, tức là có thể được thực thi thông qua lời gọi từ các tác vụ thay vì chạy thủ công bởi người dùng. Và eval cũng chỉ là một trong các phương pháp đơn giản để thực thi chương trình, tương tự như khi chúng ta sử dụng irb.

code = "Time.now"
result = eval code
puts result
=>  2016-10-22 23:22:44 +0700

Như trong ví dụ bên trên, một biến kiểu string được khởi tạo, và có giá trị là một thứ gì đó trông có vẻ giống như một câu lệnh. Nhưng ở đây câu lệnh đó không được thực thi, mà vẫn chỉ là một biến string. Câu lệnh này chỉ được thực thi khi chúng ta gọi lệnh eval, và sau đó gán kết quả của việc thực thi câu lệnh vào biến result. Lệnh eval có chức năng như một thông dịch viên, nó biến đổi một string thành một câu lệnh, và sau đó thực thi nó. Kết quả trả về của câu lệnh đó sẽ là giá trị nhận được cuối cùng khi thực thi câu lệnh.

Chúng ta có thể sử dụng eval để xem trước giá trị của các biến đã được khai báo trong chương trình, ví dụ:

a = 11
b = 12
puts eval("a + b")

Không chỉ có thế, như bạn đã thấy, chúng ta có thể truyền biến hoặc hàm vào eval() và cũng có thể làm điều ngược lại: khai báo phương thức, thậm chí cả class vào trong eval():

def a
  eval('class C;  def test() puts("ok") end  end')
end

C.new.test   # uninitialized constant C
a()          # define class C and C#test
C.new.test   # shows ok

Như trong ví dụ bên trên, một class C được định nghiã trong method a, nhưng ta không hề biết đến sự tồn tại của nó cho đến khi phương thức a được thực thi, tức là ta đã chạy eval() để khai báo class C đó trong hệ thống.

Nói một cách ngắn gọn, eval có chức năng của một trình biên dịch, nó thực thi đoạn code mà chúng ta truyền vào, trả về kết quả của đoạn code đó nếu việc thực thi thành công hoặc exception nếu như xảy ra lỗi. Tương tự như những gì chúng ta nhìn thấy trên console hoặc debugger.

2- Lợi ích và một số hạn chế của việc sử dụng eval

  • Điểm mạnh dễ thấy nhất của eval là nó cho phép chúng ta thực thi một chương trình một cách nhanh nhất có thể, hay có thể coi như "running code on the fly". Một developer có thể chạy trực tiếp một số lệnh của chương trình và kiểm tra kết quả của nó, thay vì ngồi viết toàn bộ chương trình và sử dụng các phương pháp debug để tìm ra lỗi của chương trình đó. Như bạn đã thấy ở ví dụ bên trên, ta có thể xem trước kết quả thực thi của một hàm, hay giá trị của biến mà không nhất thiết cần đến một debugger. Hoặc cũng có thể tùy biến việc khai báo phương thức hay class chỉ sử dụng trong một số trường hợp nhất định.
  • Điểm mạnh kế tiếp của eval đó là nó cho phép chúng ta rút ngắn chương trình, thay vì phải viết nhiều câu lệnh để thực hiện các công việc có cùng form chức năng, chúng ta có thể sử dụng eval() để gói gọn chúng vào một câu lệnh duy nhất. Ví dụ, tôi có một biến instance cần khởi tạo và gán giá trị trong khi tôi chưa biết chính xác biến mà mình sẽ khởi tạo sẽ có tên là gì. Thay vì phải viết một đoạn dài các câu lệnh witchhoặc if để check thì ta có thể sử dụng eval như sau:
eval("@#{arg} = arg")
  • Tuy nhiên, việc sử dụng eval có thể gây nguy hiểm cho hệ thống của bạn nếu một ai đó chèn một đoạn code phá hoại hệ thống hoặc lấy cắp thông tin vào trong lệnh eval(). Ví dụ:
eval("exit()")
  • Ngoài ra, việc lạm dụng eval sẽ khiến code của bạn khó hiểu hơn, điều rất nên tránh trong lập trình.

Như vậy, tùy vào từng trường hợp cụ thể, bạn hãy cân nhắc kĩ đến việc có nên hay không nên sử dụng eval().


All rights reserved

Viblo
Hãy đăng ký một tài khoản Viblo để nhận được nhiều bài viết thú vị hơn.
Đăng kí