Computer >> คอมพิวเตอร์ >  >> การเขียนโปรแกรม >> Ruby

การบันทึกตัวแปรท้องถิ่นและอินสแตนซ์เมื่อมีข้อยกเว้นใน Ruby

คุณเคยมีข้อผิดพลาดที่คุณไม่สามารถทำซ้ำได้อย่างง่ายดายหรือไม่? ดูเหมือนว่าจะเกิดขึ้นเมื่อมีคนใช้แอปของคุณมาระยะหนึ่งแล้วเท่านั้น และข้อความแสดงข้อผิดพลาดและการติดตามย้อนกลับก็ไม่ช่วยอะไรอย่างน่าประหลาดใจ

ช่วงเวลาเช่นนี้จะมีประโยชน์มากหากคุณถ่ายภาพสแนปชอตของสถานะของแอปก่อนเกิดข้อยกเว้น ตัวอย่างเช่น หากคุณมีรายการตัวแปรท้องถิ่นทั้งหมดและค่าของตัวแปรเหล่านั้น คุณสามารถทำได้ - และมันก็ไม่ยากเลย!

ในโพสต์นี้ ฉันจะแสดงวิธีจับภาพคนในท้องถิ่นในเวลาที่มีข้อยกเว้น แต่ก่อนอื่นฉันต้องเตือนคุณ ไม่ควรใช้เทคนิคเหล่านี้ในการผลิต คุณสามารถใช้พวกมันในการแสดงละคร พรีผลิตภัณฑ์ การพัฒนา ฯลฯ ไม่ใช่แค่การผลิต อัญมณีที่เราจะใช้นั้นขึ้นอยู่กับเวทย์มนตร์วิปัสสนาที่ค่อนข้างหนักซึ่งอย่างดีที่สุดจะทำให้แอปของคุณช้าลง แย่ที่สุด...ใครจะรู้?

แนะนำการผูก_of_caller

Binding_of_caller gem ให้คุณเข้าถึงการผูกสำหรับระดับใดๆ ของสแต็คปัจจุบัน ซู่.....หมายความว่าไงเนี่ย

"สแต็ก" เป็นเพียงรายการเมธอดที่ "อยู่ในระหว่างดำเนินการ" ในปัจจุบัน คุณสามารถใช้ caller วิธีการตรวจสอบสแต็กปัจจุบัน นี่เป็นตัวอย่างง่ายๆ:

def a
  puts caller.inspect # ["caller.rb:20:in `<main>'"]
  b()
end

def b
  puts caller.inspect # ["caller.rb:4:in `a'", "caller.rb:20:in `<main>'"]
  c()
end

def c
  puts caller.inspect # ["caller.rb:11:in `b'", "caller.rb:4:in `a'", "caller.rb:20:in `<main>'"]
end

a()

การเชื่อมโยงเป็นสแนปชอตของบริบทการดำเนินการปัจจุบัน ในตัวอย่างด้านล่าง ฉันจับการเชื่อมโยงของเมธอด จากนั้นใช้เพื่อเข้าถึงตัวแปรในเครื่องของเมธอด

def get_binding
  a = "marco"
  b = "polo"
  return binding
end

my_binding = get_binding

puts my_binding.local_variable_get(:a) # "marco"
puts my_binding.local_variable_get(:b) # "polo"

Binding_of_caller gem ให้คุณเข้าถึงการผูกสำหรับระดับใด ๆ ของสแต็คการดำเนินการปัจจุบัน ตัวอย่างเช่น ฉันสามารถใช้เพื่ออนุญาต c วิธีการเข้าถึง a ตัวแปรท้องถิ่นของเมธอด

require "rubygems"
require "binding_of_caller"

def a
  fruit = "orange"
  b()
end

def b
  fruit = "apple"
  c()
end

def c
  fruit = "pear"

  # Get the binding "two levels up" and ask it for its local variable "fruit"
  puts binding.of_caller(2).local_variable_get(:fruit) 
end

a() # prints "orange"

ณ จุดนี้ คุณอาจรู้สึกสองอารมณ์ที่ขัดแย้งกัน ตื่นเต้นเพราะมันเจ๋งจริงๆ และความรังเกียจ เพราะสิ่งนี้อาจทำให้การพึ่งพาอาศัยกันที่ยุ่งเหยิงแย่ลงได้เร็วกว่าที่คุณพูด DHH

การบันทึกในพื้นที่ในเวลาที่มีการยกเว้น

ตอนนี้เราได้เข้าใจถึงการผูกมัด_of_caller แล้ว การบันทึกตัวแปรโลคัลทั้งหมดในเวลาที่มีข้อยกเว้นนั้นไม่ใช่เรื่องยาก ในตัวอย่างด้านล่าง ฉันกำลังแทนที่วิธีการเพิ่ม วิธีการเพิ่มใหม่ของฉันดึงการเชื่อมโยงของวิธีการใดก็ตามที่เรียกว่ามัน จากนั้นจะทำซ้ำผ่านคนในท้องถิ่นทั้งหมดและพิมพ์ออกมา

require "rubygems"
require "binding_of_caller"

module LogLocalsOnRaise
  def raise(*args)
    b = binding.of_caller(1)
    b.eval("local_variables").each do |k|
      puts "Local variable #{ k }: #{ b.local_variable_get(k) }"
    end
    super
  end
end

class Object
  include LogLocalsOnRaise
end

def buggy
  s = "hello world"
  raise RuntimeError
end

buggy()

นี่คือลักษณะการใช้งานจริง:

การบันทึกตัวแปรท้องถิ่นและอินสแตนซ์เมื่อมีข้อยกเว้นใน Ruby

การออกกำลังกาย:บันทึกตัวแปรอินสแตนซ์

ฉันจะปล่อยให้มันเป็นแบบฝึกหัดสำหรับคุณในการบันทึกตัวแปรอินสแตนซ์ควบคู่ไปกับคนในพื้นที่ คำแนะนำ:คุณสามารถใช้ my_binding.eval("instance_variables") และ my_binding.instance_variable_get ในลักษณะเดียวกับที่คุณใช้ my_binding.eval("local_variables") และ my_binding.instance_variable_get .

วิธีที่ง่าย

นี่เป็นเคล็ดลับที่เจ๋งมาก แต่การวนรอบไฟล์บันทึกไม่ใช่วิธีที่สะดวกที่สุดในการแก้ไขจุดบกพร่อง โดยเฉพาะอย่างยิ่งหากแอปของคุณอยู่ในขั้นตอนการทำงานและคุณมีผู้ใช้หลายคน นอกจากนี้ยังเป็นเพียงแค่โค้ดเพิ่มเติมที่คุณต้องรักษาไว้

หากคุณบังเอิญใช้ Honeybadger เพื่อตรวจสอบแอปของคุณเพื่อหาข้อผิดพลาด เราสามารถจับภาพคนในพื้นที่ได้โดยอัตโนมัติ สิ่งที่คุณต้องทำคือเพิ่ม binding_of_caller gem ลงใน Gemfile ของคุณ:

# Gemfile

group :development, :staging do
  # Including this gem enables local variable capture via Honeybadger
  gem "binding_of_caller"
  ...
end

ตอนนี้ เมื่อใดก็ตามที่เกิดข้อยกเว้น คุณจะได้รับรายงานของคนในพื้นที่ทั้งหมดพร้อมกับ backtrace, params ฯลฯ

การบันทึกตัวแปรท้องถิ่นและอินสแตนซ์เมื่อมีข้อยกเว้นใน Ruby