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

วิธีลองอีกครั้งเมื่อเกิดข้อยกเว้นใน Ruby

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

แนะนำ retry

โอเค อันนี้ค่อนข้างชัดเจน แต่ถ้าคุณรู้ว่ามันมีอยู่จริง โดยส่วนตัวแล้ว ฉันคุ้นเคยกับอาชีพ Ruby เป็นอย่างดีก่อนที่ฉันจะได้เรียนรู้เกี่ยวกับคีย์เวิร์ด "ลองใหม่" ที่น่ายินดี

ลองใหม่อีกครั้งถูกสร้างขึ้นในระบบการช่วยเหลือข้อยกเว้นของ Ruby มันค่อนข้างง่าย หากคุณใช้ "ลองใหม่" ในบล็อกการกู้คืน จะทำให้ส่วนโค้ดที่ได้รับการช่วยเหลือกลับมาทำงานอีกครั้ง มาดูตัวอย่างกัน

begin
  retries ||= 0
  puts "try ##{ retries }"
  raise "the roof"
rescue
  retry if (retries += 1) < 3
end

# ... outputs the following:
# try #0
# try #1
# try #2

มีบางสิ่งที่ควรทราบที่นี่:

  • เมื่อเรียกลองใหม่ ทั้งหมด ของรหัสระหว่างการเริ่มต้นและการกู้คืนจะถูกเรียกใช้อีกครั้ง แน่นอนที่สุด ใช่ ไม่ "ไปต่อจากที่ค้างไว้" หรืออะไรทำนองนั้น

  • หากคุณไม่มีกลไกบางอย่างในการจำกัดการลองใหม่ คุณจะจบลงด้วยการวนซ้ำที่ไม่สิ้นสุด

  • โค้ดทั้งในบล็อกเริ่มต้นและบล็อกช่วยเหลือสามารถเข้าถึงตัวแปรการลองใหม่เดียวกันในขอบเขตหลักได้

ปัญหาเกี่ยวกับ retry

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

ตัวอย่างเช่น ลองจินตนาการว่าคุณกำลังใช้อัญมณีที่ให้คุณโพสต์การอัปเดตสถานะไปยัง Twitter, Facebook และเว็บไซต์อื่นๆ ได้โดยใช้การเรียกใช้เมธอดเดียว อาจมีลักษณะเช่นนี้

SocialMedia.post_to_all("Zomg! I just ate the biggest hamburger")

# ...posts to Twitter API
# ...posts to Facebook API
# ...etc

หาก API ตัวใดตัวหนึ่งไม่ตอบสนอง เจมจะสร้าง SocialMedia::TimeoutError และยกเลิก หากเราตรวจพบข้อยกเว้นนี้และลองอีกครั้ง เราจะจบลงด้วยการโพสต์ที่ซ้ำกันเนื่องจากการลองใหม่จะเริ่มใหม่ตั้งแต่ต้น

begin
  SocialMedia.post_to_all("Zomg! I just ate the biggest hamburger")
rescue SocialMedia::TimeoutError
  retry
end

# ...posts to Twitter API
# facebook error
# ...posts to Twitter API
# facebook error
# ...posts to Twitter API
# and so on

คงจะดีไม่น้อยถ้าเราสามารถบอกอัญมณีว่า "เพียงแค่ข้าม facebook และไปที่รายการ API ที่จะโพสต์ต่อไป"

โชคดีสำหรับเรา Ruby ช่วยให้เราทำอย่างนั้นได้

หมายเหตุ:แน่นอนว่าวิธีแก้ปัญหาที่แท้จริงคือการสร้างไลบรารีโซเชียลมีเดียใหม่ แต่นี่ยังห่างไกลจากกรณีการใช้งานเพียงอย่างเดียวสำหรับเทคนิคที่ฉันจะแสดงให้คุณเห็น

ความต่อเนื่องในการช่วยชีวิต

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

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

...โอเค มันไม่ใช่การเปรียบเทียบที่สมบูรณ์แบบ แต่ใช้งานได้ดี มาดูโค้ดกัน:

require "continuation"
counter = 0
continuation = callcc { |c| c } # define our savepoint
puts(counter += 1)
continuation.call(continuation) if counter < 5 # jump back to our savepoint

คุณอาจสังเกตเห็นสิ่งแปลก ๆ บางอย่าง ผ่านมันไปกันเถอะ:

  • เราใช้เมธอด callcc เพื่อสร้างออบเจ็กต์ Continuation ไม่มีไวยากรณ์ OO ที่สะอาดสำหรับสิ่งนี้

  • ครั้งแรกที่ continuation กำหนดตัวแปรไว้ โดยกำหนดให้เป็นค่าส่งคืนของ callcc บล๊อก. นั่นเป็นเหตุผลที่ต้องมีบล็อกอยู่ที่นั่น

  • ทุกครั้งที่เรากระโดดกลับไปที่จุดบันทึก continuation ตัวแปรถูกกำหนดอาร์กิวเมนต์ที่เราส่ง call กระบวนการ. นั่นเป็นเหตุผลที่เราใช้ continuation.call(continuation) . ที่ดูแปลกตา ไวยากรณ์

การเพิ่มความต่อเนื่องให้กับข้อยกเว้น

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

begin
  raise "the roof"
  puts "The exception was ignored"
rescue => e
  e.skip
end

# ...outputs "The exception was ignored"

การทำเช่นนี้ฉันจะต้องทำบาปเล็กน้อย Exception เป็นเพียงชั้นเรียน นั่นหมายความว่าฉันสามารถ monekypatch เพื่อเพิ่ม skip วิธีการ

class Exception
  attr_accessor :continuation
  def skip
    continuation.call
  end
end

ตอนนี้เราต้องตั้งค่า continuation คุณลักษณะสำหรับทุกข้อยกเว้น ปรากฎว่า raise เป็นเพียงวิธีการซึ่งเราสามารถแทนที่ได้

BTW รหัสด้านล่างใช้เกือบทุกคำจากสไลด์ที่ยอดเยี่ยมของ Advi สิ่งที่คุณไม่รู้เกี่ยวกับข้อยกเว้น ฉันไม่สามารถคิดหาวิธีที่ดีไปกว่านี้:

require 'continuation'
module StoreContinuationOnRaise
  def raise(*args)
    callcc do |continuation|
      begin
        super
      rescue Exception => e
        e.continuation = continuation
        super(e)
      end
    end
  end
end

class Object
  include StoreContinuationOnRaise
end

ตอนนี้ฉันสามารถเรียก skip วิธีการสำหรับข้อยกเว้นใด ๆ และมันจะเป็นเหมือนข้อยกเว้นที่ไม่เคยเกิดขึ้น