सभी एरर घातक नहीं होते। कुछ केवल यह संकेत देते हैं कि आपको फिर से प्रयास करने की आवश्यकता है। सौभाग्य से, रूबी कुछ दिलचस्प तंत्र प्रदान करता है जो "फिर से प्रयास करना" आसान बनाते हैं - हालांकि उनमें से सभी स्पष्ट या प्रसिद्ध नहीं हैं। इस पोस्ट में हम इन तंत्रों पर एक नज़र डालेंगे और वे वास्तविक दुनिया में कैसे काम करते हैं।
retry का परिचय
ठीक है - यह स्पष्ट है, लेकिन केवल अगर आप जानते हैं कि यह मौजूद है। व्यक्तिगत रूप से, मैं अपने रूबी करियर में काफी आगे बढ़ चुका था इससे पहले कि मुझे आनंददायक "retry" कीवर्ड के बारे में पता चला।
Retry रूबी के एक्सेप्शन रेस्क्यूइंग सिस्टम में अंतर्निहित है। यह काफी सरल है। यदि आप अपने रेस्क्यू ब्लॉक में "retry" का उपयोग करते हैं तो यह उस कोड खंड को फिर से चलाता है जिसे रेस्क्यू किया गया था। आइए एक उदाहरण देखें।
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 को कॉल किया जाता है, तो सभी कोड जो begin और rescue के बीच में है, फिर से चलाया जाता है। यह सबसे निश्चित रूप से नहीं "वहीं से शुरू होता है जहां यह छूट गया था" या ऐसा कुछ।
यदि आप रीट्राई को सीमित करने के लिए कोई तंत्र प्रदान नहीं करते हैं, तो आप अनंत लूप के साथ समाप्त हो जाएंगे।
begin और रेस्क्यू ब्लॉक दोनों में कोड पैरेंट स्कोप में समान retries वेरिएबल तक पहुंचने में सक्षम हैं।
retry के साथ समस्या
जबकि retry बहुत अच्छा है, इसकी कुछ सीमाएं हैं। मुख्य यह है कि पूरा begin ब्लॉक फिर से चलाया जाता है। लेकिन कभी-कभी यह आदर्श नहीं होता।
उदाहरण के लिए, कल्पना करें कि आप एक जेम का उपयोग कर रहे हैं जो आपको ट्विटर, फेसबुक और कई अन्य साइटों पर स्टेटस अपडेट पोस्ट करने देता है एक ही मेथड कॉल का उपयोग करके। यह कुछ इस तरह दिख सकता है।
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
क्या यह अच्छा नहीं होगा अगर हम जेम को बता सकें "बस फेसबुक को छोड़ दो, और API की सूची में पोस्ट करना जारी रखो।"
सौभाग्य से हमारे लिए, रूबी हमें ठीक ऐसा करने की अनुमति देता है।
नोट: बेशक इस समस्या का वास्तविक समाधान सोशल मीडिया लाइब्रेरी को पुनर्गठित करना है। लेकिन यह उन तकनीकों के लिए एकमात्र उपयोग-मामला नहीं है जो मैं आपको दिखाने जा रहा हूँ।
कंटिन्यूएशन से रेस्क्यू
कंटिन्यूएशन लोगों को डराते हैं। लेकिन यह सिर्फ इसलिए है क्योंकि उनका उपयोग अक्सर नहीं किया जाता और वे थोड़े अजीब दिखते हैं। लेकिन एक बार जब आप मूल बातें समझ लेते हैं तो वे वास्तव में काफी सरल होते हैं।
एक कंटिन्यूएशन आपके कोड में "सेव पॉइंट" की तरह है, बिल्कुल वीडियो गेम की तरह। आप जाकर अन्य काम कर सकते हैं, फिर सेव पॉइंट पर वापस कूद सकते हैं और सब कुछ वैसा ही होगा जैसा आपने छोड़ा था।
...ठीक है, तो यह एक आदर्श सादृश्य नहीं है, लेकिन यह कुछ हद तक काम करता है। आइए कुछ कोड देखें:
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 मेथड का उपयोग करते हैं। इसके लिए कोई साफ 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 सिर्फ एक क्लास है। इसका मतलब है कि मैं इसमें skip मेथड जोड़ने के लिए मंकीपैच कर सकता हूँ।
class Exception attr_accessor :continuation def skip continuation.call end end
अब हमें हर एक्सेप्शन के लिए continuation एट्रिब्यूट सेट करने की आवश्यकता है। पता चला है कि raise सिर्फ एक मेथड है, जिसे हम ओवरराइड कर सकते हैं।
BTW, नीचे दिया गया कोड Advi के उत्कृष्ट स्लाइड डेक Things You Didn't know about Exceptions से लगभग शब्दशः लिया गया है। मैं बस इसके कार्यान्वयन का इससे बेहतर तरीका नहीं सोच सका:
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 मेथड को कॉल कर सकता हूँ और यह ऐसा होगा जैसे एक्सेप्शन कभी हुआ ही नहीं।