Một số thủ thuật Debug code Ruby
Bài viết này bao gồm một số thủ thuật để giúp các bạn có thể tìm hiểu và debug code ruby một cách hiệu quả hơn. Tìm vị trí mà một method được định nghĩa Sử dụng hàm method của Object: object = Object . new puts object . method ( :blank? ) . source_location = > / home ...
Bài viết này bao gồm một số thủ thuật để giúp các bạn có thể tìm hiểu và debug code ruby một cách hiệu quả hơn.
Tìm vị trí mà một method được định nghĩa
Sử dụng hàm method của Object:
object = Object.new puts object.method(:blank?).source_location => /home/hatt/.rvm/gems/ruby-2.3.1/gems/activesupport-5.0.0/lib/active_support/core_ext/object/blank.rb => 14
Hàm blank? này được định nghĩa ở dòng 14 của file active_support/core_ext/object/blank.rb.
Mở thư mục của một gem được sử dụng trong hệ thống
bundle open active_support
Câu lệnh trên sẽ mở thư mục chứa một phiên bản của active_support được định nghĩa trong Gemfile.lock. Từ đây, bạn có thể đặt các câu lệnh debug (pry, bye_bug...) vào code và tìm hiểu xem gem hoạt động như thế nào. Câu lệnh này sẽ sử dụng giá trị từ biến môi trường EDITOR. Giá trị mặc định có thể là vi hoặc bạn có thể thiết lập nó trong .bashrc.
Reset Gem về lại thời điểm ban đầu
Nếu bạn mở một gem và chỉnh sửa trong đó nhưng lại quên không "check out" lại, những thay đổi của bạn sẽ ảnh hưởng đến những lần chạy sau của gem. Để reset về lại như ban đầu, bạn có thể chạy lệnh gem pristine. Để reset gem active_support, ta làm như sau:
$ gem pristine activesupport
Để reset toàn bộ gem, bạn có thể chạy lệnh sau:
$ gem pristine --all
Câu lệnh trên có thể sẽ tốn nhiều thời gian, đặc biệt nếu như bạn có gem với c-extensions
Tìm hiểu xem một hàm được gọi như thế nào
Để tạo ra một backtrace mà không muốn có exception, các bạn có thể dùng hàm caller như sau:
class Project def foo puts caller end end
Khi hàm foo được gọi thì nó sẽ sinh ra một backtrace tương tự như sau:
===================== (pry):8:in `__pry__' /home/hatt/.rvm/gems/ruby-2.3.1/gems/pry-0.10.3/lib/pry/pry_instance.rb:355:in `eval' /home/hatt/.rvm/gems/ruby-2.3.1/gems/pry-0.10.3/lib/pry/pry_instance.rb:355:in `evaluate_ruby' /home/hatt/.rvm/gems/ruby-2.3.1/gems/pry-0.10.3/lib/pry/pry_instance.rb:323:in `handle_line' /home/hatt/.rvm/gems/ruby-2.3.1/gems/pry-0.10.3/lib/pry/pry_instance.rb:243:in `block (2 levels) in eval' /home/hatt/.rvm/gems/ruby-2.3.1/gems/pry-0.10.3/lib/pry/pry_instance.rb:242:in `catch' /home/hatt/.rvm/gems/ruby-2.3.1/gems/pry-0.10.3/lib/pry/pry_instance.rb:242:in `block in eval' /home/hatt/.rvm/gems/ruby-2.3.1/gems/pry-0.10.3/lib/pry/pry_instance.rb:241:in `catch' /home/hatt/.rvm/gems/ruby-2.3.1/gems/pry-0.10.3/lib/pry/pry_instance.rb:241:in `eval' /home/hatt/.rvm/gems/ruby-2.3.1/gems/pry-0.10.3/lib/pry/repl.rb:77:in `block in repl' /home/hatt/.rvm/gems/ruby-2.3.1/gems/pry-0.10.3/lib/pry/repl.rb:67:in `loop' /home/hatt/.rvm/gems/ruby-2.3.1/gems/pry-0.10.3/lib/pry/repl.rb:67:in `repl' /home/hatt/.rvm/gems/ruby-2.3.1/gems/pry-0.10.3/lib/pry/repl.rb:38:in `block in start' /home/hatt/.rvm/gems/ruby-2.3.1/gems/pry-0.10.3/lib/pry/input_lock.rb:61:in `__with_ownership' /home/hatt/.rvm/gems/ruby-2.3.1/gems/pry-0.10.3/lib/pry/input_lock.rb:79:in `with_ownership' /home/hatt/.rvm/gems/ruby-2.3.1/gems/pry-0.10.3/lib/pry/repl.rb:38:in `start' /home/hatt/.rvm/gems/ruby-2.3.1/gems/pry-0.10.3/lib/pry/repl.rb:15:in `start' /home/hatt/.rvm/gems/ruby-2.3.1/gems/pry-0.10.3/lib/pry/pry_class.rb:169:in `start' /home/hatt/.rvm/gems/ruby-2.3.1/gems/railties-5.0.0/lib/rails/commands/console.rb:65:in `start' /home/hatt/.rvm/gems/ruby-2.3.1/gems/railties-5.0.0/lib/rails/commands/console_helper.rb:9:in `start' /home/hatt/.rvm/gems/ruby-2.3.1/gems/railties-5.0.0/lib/rails/commands/commands_tasks.rb:78:in `console' /home/hatt/.rvm/gems/ruby-2.3.1/gems/railties-5.0.0/lib/rails/commands/commands_tasks.rb:49:in `run_command!' /home/hatt/.rvm/gems/ruby-2.3.1/gems/railties-5.0.0/lib/rails/commands.rb:18:in `<top (required)>' bin/rails:4:in `require' bin/rails:4:in `<main>'
Dòng đầu tiên là chỉ hàm được gọi trước khi puts caller được gọi. Các bạn có thể thấy là hàm foo của tôi được gọi tử pry console dựa vào dòng đầu tiên của backtrace phía trên:
(pry):8:in `__pry__`
Nều bạn để ý toàn bộ backtrace thì sẽ thấy cách mà pry hoạt động và xử lý input đưa vào console như thế nào (có thể ở một bài viết khác sẽ đề cập tới vấn đề này).
Tìm xem super được gọi từ đâu
Trong ruby bạn có thể include nhiều module vào một object, mà đội khi các module cũng có thể có các hàm bị trùng tên mà bạn lại sử dụng super trên những hàm này. Vì vậy, việc tìm source location của super khá là hữu ích. Các bạn có thể sử dụng các sau:
def foo puts method(:foo).super_method.source_location super end
Liệt kê toàn bộ các hàm của object
object.methods
Tìm vị trí của một hàm của instance mà không phải tạo một instance mới
User.instance_method(:foo).source_location
Liệt kê các tham số của hàm
def parse(input, skip_code_comments: false, ignore_whitespace: true) #... end
method(:parse).parameters # => [[:req, :input], [:key, :skip_code_comments], [:key, :ignore_whitespace]]
Tìm vị trí mà một tham số bị thay đổi giá trị
Thông thường khi chúng ta khởi tạo một biến như sau:
config.thing = {"foo" => "bar"}
Tuy nhiên, ở những đoạn code sau đó thì giá trị của nó lại bị thay đổi như sau:
puts config.thing # => {"foo" => "THIS VALUE IS DIFFERENT"}
Các bạn có thể xem đoạn code nào thay đổi giá trih này bằng cách sử dụng freeze lên chính giá trị đó. Đoạn code nào thay đổi giá trị chắc chắn sẽ bắn ra exceptions và có backtrace để ta thấy được vị trí mà giá trị bị thay đổi.
Kỹ thuật này sẽ không hoạt động trong trường hợp biến được gán giá trị mới tuy nhiên nó cũng đáng để thử và giúp ta loại trừ được một trường hợp khi debug.
Tìm vị trí mà constant được tạo
Các bạn có thể sử dụng object space để xem một constant hoặc object được khởi tạo ở đâu:
require 'objspace' ObjectSpace.trace_object_allocations_start Kernel.send(:define_method, :sup) do |obj| puts "#{ ObjectSpace.allocation_sourcefile(obj) }:#{ ObjectSpace.allocation_sourceline(obj) }" end world = "hello" sup world # => (pry):14
Do đoạn code để lấy tên file và số dòng khá dài nên hàm sup được định nghĩa để thu gọn đoạn code đó lại. Các bạn có thể thấy là object world được định nghĩa trong pry console dòng 14.
Kết bài
Trên đây là những thủ thuật giúp các bạn có thể debug hoặc đọc và tìm hiểu xem code ruby được chạy như thế nào trong dự án của mình cũng như là trong những gem chúng ta hay sử dụng. Nếu các bạn muốn tìm hiểu sâu về source code của gem mình đang sử dụng thì những thủ thuật trên sẽ rất hữu ích đối với bạn.
References: Ruby Debugging Magic Cheat Sheet