आदर्श दुनिया में सब कुछ बिल्कुल ठीक चलता है। लेकिन वास्तविक दुनिया में चीज़ें गड़बड़ा सकती हैं, और ऐसी स्थितियों को हम कैसे संभालते हैं, यह इस बात के लिए मायने रखता है कि हमारा सॉफ्टवेयर मज़बूत और भरोसेमंद बना रहे। एक्सेप्शन प्रोग्रामिंग की एक बहुत ज़रूरी अवधारणा है, जिसकी मदद से हम एरर और अप्रत्याशित स्थितियों को सहजता से संभाल सकते हैं।
एरर उठाने पर, अगर उसे संभाला न जाए, तो प्रोग्राम रुक जाता है और एक एरर संदेश दिखाता है। ज़्यादातर मामलों में आप नहीं चाहते कि एरर आने पर आपका प्रोग्राम रुक जाए; आप चाहते हैं कि एरर को संभालकर प्रोग्राम चलता रहे।
Crystal में एक्सेप्शन raise कीवर्ड की मदद से उठाए जाते हैं, और उन्हें या तो कोई String या कोई Exception ऑब्जेक्ट दिया जा सकता है।
अगर उसे संभाला न जाए, तो प्रोग्राम रुक जाएगा और एरर संदेश छाप देगा।
raise "This is an error"
Crystal में कई बिल्ट-इन एक्सेप्शन मौजूद हैं, जैसे ArgumentError, IndexError, KeyError, IOError, SystemCallError, TypeError, ZeroDivisionError और भी बहुत से।
इनमें आपको एक्सेप्शन को एक संदेश देना पड़ता है।
raise ArgumentError.new("This is an argument error")
हम नहीं चाहेंगे कि एक्सेप्शन उठने पर हमारा प्रोग्राम क्रैश हो जाए।
इसलिए, जब हमें पता हो कि कोड के किसी हिस्से में एरर की गुंजाइश है, तो हम उसे एक begin ब्लॉक में लपेट सकते हैं और rescue ब्लॉक की मदद से उस एक्सेप्शन को संभाल सकते हैं।
begin ब्लॉक उस कोड की शुरुआत बताता है जिसमें एक्सेप्शन उठ सकता है, और rescue ब्लॉक उस एक्सेप्शन को संभालता है।
begin
raise "This is an error"
rescue
puts "An error occurred!"
end
rescue ब्लॉक के साथ एक वेरिएबल भी दे सकते हैं, जिससे एक्सेप्शन ऑब्जेक्ट मिल जाता है।
begin
raise "This is an error"
rescue ex
puts "An error occurred: #{ex.message}"
end
rescue ब्लॉक के साथ कोई खास एक्सेप्शन टाइप भी दे सकते हैं, ताकि सिर्फ वही एक्सेप्शन पकड़ा जाए।
begin
raise ArgumentError.new("This is an argument error")
rescue ArgumentError
puts "An argument error occurred!"
end
# or
begin
raise ArgumentError.new("This is an argument error")
rescue ex : ArgumentError
puts "An argument error occurred: #{ex.message}"
end
अलग-अलग तरह के एक्सेप्शन संभालने के लिए कई rescue ब्लॉक इस्तेमाल कर सकते हैं।
नीचे के उदाहरण में पहला rescue ब्लॉक ArgumentError पकड़ेगा, और दूसरा rescue ब्लॉक बाकी किसी भी एक्सेप्शन को पकड़ेगा।
begin
raise ArgumentError.new("This is an argument error")
rescue ArgumentError
puts "An argument error occurred!"
rescue
puts "An error occurred!"
end
begin ब्लॉक के साथ एक else ब्लॉक भी हो सकता है, जो तब चलता है जब कोई एक्सेप्शन न उठे।
begin
puts "No error occurred"
rescue
puts "An error occurred!"
else
puts "No error occurred"
end
अंत में एक ensure ब्लॉक होता है, जो हमेशा चलता है, चाहे एक्सेप्शन उठा हो या नहीं।
begin
raise "This is an error"
rescue
puts "An error occurred!"
ensure
puts "This is always executed"
end
कुछ मेथड के दो वर्शन होते हैं, एक ! के साथ और दूसरा उसके बिना।
इसका मतलब दो अलग-अलग चीज़ें हो सकता है।
एक यह कि मेथड ऑब्जेक्ट को बदल देता है, और दूसरा यह कि मेथड एक्सेप्शन उठा सकता है।
लेकिन बूलियन कॉन्सेप्ट में बताया गया एक और कन्वेंशन भी है, जो मेथड के नाम के अंत में ? लगाने से जुड़ा है।
कुछ मेथड डिफॉल्ट रूप से एक्सेप्शन उठाते हैं, लेकिन उनका ? पर खत्म होने वाला एक वर्शन भी होता है, जो एक्सेप्शन उठाने की जगह nil लौटाता है।
यह तब बहुत काम का है जब आप नहीं चाहते कि कोई एरर उठे। इससे कार्यक्षमता को फायदा हो सकता है, क्योंकि स्टैकट्रेस बनाने की ज़रूरत नहीं पड़ती, और सही तरीके से इस्तेमाल करने पर यह कोड को ज़्यादा सुरक्षित भी बना सकता है।
आप Exception क्लास से इनहेरिट करके अपने खुद के एक्सेप्शन भी बना सकते हैं।
ऐसा करते समय आप चाहें तो initialize मेथड को ओवरराइड करके एक्सेप्शन का संदेश तय कर सकते हैं।
इसके लिए @message नाम के इंस्टेंस वेरिएबल को वह संदेश असाइन किया जाता है।
class MyException < Exception
def initialize
@message = "This is my exception"
end
end
raise MyException.new
जिस दिन का आपको इतने लंबे समय से इंतज़ार था, वह दिन अब आ गया है, और अब आप आल्प्स के एक सुंदर फार्म के गर्वित मालिक हैं।
आपको अब भी सुबह बहुत जल्दी उठकर अपनी गायों को चारा खिलाना पसंद नहीं है। चूँकि आप एक बेहतरीन इंजीनियर हैं, इसलिए आप एक खाना बाँटने वाली मशीन बनाते हैं, जिसका नाम FEED-M-ALL है।
अपना प्रोजेक्ट पूरा करने के लिए अब बस एक कोड की ज़रूरत बाकी है, जो हिसाब लगाए कि हर गाय को कितना चारा मिलना चाहिए। यह ज़रूरी है कि हर गाय को बराबर मात्रा मिले, इसलिए झगड़ों से बचना होगा। गायें बहुत संवेदनशील होती हैं।
अच्छी बात यह है कि चारे की मात्रा निकालने के सारे फॉर्मूले आपको खुद नहीं बनाने हैं। आप इंटरनेट पर मिली एक रहस्यमयी बाहरी लाइब्रेरी इस्तेमाल करते हैं। ऐसा माना जाता है कि इससे गायें सबसे ज़्यादा खुश रहेंगी। यह लाइब्रेरी एक क्लास देती है, जो नीचे दिया गया इंटरफेस पूरा करती है। अपने लिखे कोड में आप इसी पर भरोसा करेंगे।
class FodderCalculator
def fodder_amount! : Number
end
def fattening_factor! : Number
end
end
जैसे-जैसे आप अपना कोड लिखते जाएँगे, आप एरर को संभालने का तरीका बेहतर बनाते जाएँगे, ताकि वह ज़्यादा मज़बूत बने और रोज़ के खेत के काम में इस्तेमाल करते समय बाद में उसे डीबग करना आसान रहे।
सबसे पहले आप उस कोड को लिखने पर ध्यान देते हैं, जो हर गाय के हिस्से का चारा निकालने के लिए ज़रूरी है।
एक मेथड TheFarm.divide_food बनाइए, जो आर्गुमेंट के तौर पर एक FodderCalculator ऑब्जेक्ट और गायों की संख्या एक पूर्णांक के रूप में लेता है।
इस काम के लिए आप यह मान लीजिए कि दी गई गायों की संख्या हमेशा शून्य से अधिक होती है।
इस मेथड को हर गाय के हिस्से का खाना लौटाना चाहिए।
हिसाब लगाने के लिए सबसे पहले आपको सारी गायों के लिए कुल चारे की मात्रा निकालनी होगी।
यह काम fodder_amount! मेथड को कॉल करके और उसे गायों की संख्या देकर किया जाता है।
इसके अलावा आपको एक फैक्टर चाहिए, जिससे इस मात्रा को गुणा करना है।
यह फैक्टर आपको fattening_factor! मेथड को कॉल करके मिलता है।
इन दोनों वैल्यू और गायों की संख्या की मदद से अब आप हर गाय के हिस्से का खाना (float64 के रूप में) निकाल सकते हैं।
अगर आपके कॉल किए गए इनमें से कोई मेथड एरर लौटाता है, तो कोड का चलना वहीं रुक जाना चाहिए और 0 लौटना चाहिए।
fodder_calculator = FodderCalculator.new(50, 1.5)
TheFarm.divide_food(fodder_calculator, 5)
# => 15.0
fodder_calculator = FodderCalculator.new(50, nil)
TheFarm.divide_food(fodder_calculator, 5)
# => 0
ऊपर पहला काम करते समय आपको एहसास हुआ कि आप जिस बाहरी लाइब्रेरी का इस्तेमाल कर रहे हैं, वह उतनी अच्छी नहीं है जितनी आपने सोची थी। जैसे, वह गलत इनपुट ठीक से संभाल नहीं पाती। आप अपने ही कोड में इनपुट की जाँच जोड़कर इस कमी को दूर करना चाहते हैं।
एक फंक्शन TheFarm.validate_input_and_divide_food लिखिए, जिसका सिग्नेचर ऊपर दिए गए TheFarm.divide_food जैसा ही हो।
TheFarm.divide_food कॉल करना चाहिए और उस कॉल का नतीजा लौटाना चाहिए।"Number of cows must be greater than 0" संदेश के साथ एरर उठानी चाहिए।TheFarm.validate_input_and_divide_food(fodderCalculator, 5)
# => 15
TheFarm.validate_input_and_divide_food(fodderCalculator, -2)
# Error: Number of cows must be greater than 0
गायों की संख्या आगे भेजने से पहले उसकी जाँच करना अच्छा कदम था, लेकिन यह अस्पष्ट एरर संदेश आपको खास पसंद नहीं आया।
आप एक कस्टम एरर क्लास बनाकर इसे और बेहतर करने का फैसला करते हैं, जिसका नाम InvalidCowsError होगा।
इस कस्टम एरर में गायों की संख्या (Int32) और एक कस्टम संदेश (String) रखा जाना चाहिए, और Expection मेथड को यह डेटा इस फॉर्मेट में बदलना चाहिए:
{number of cows} cows are invalid: {custom message}
अपनी कस्टम एरर के साथ अब एक फंक्शन TheFarm.validate_number_of_cows बनाइए, जो गायों की संख्या को एक पूर्णांक के रूप में लेता है और एक एरर (या nil) लौटाता है।
"there are no negative cows" कस्टम संदेश वाली एक InvalidCowsError लौटाता है।"no cows don't need food" कस्टम संदेश वाली एक InvalidCowsError लौटाता है।nil लौटाता है, जिसका मतलब है कि जाँच सफल रही।TheFarm.validate_number_of_cows(5)
# => nil
TheFarm.validate_number_of_cows(-5)
# => InvalidCowsError: -5 cows are invalid: there are no negative cows
इस जाँच फंक्शन को बनाने की कड़ी मेहनत के बाद आपको ध्यान आता है कि अब तो शाम हो चुकी है, और आप अपनी मेज़ छोड़कर पहाड़ों पर छाए सूर्यास्त का आनंद लेने निकल जाते हैं। इस नए जाँच फंक्शन को असल में अपने कोड में जोड़ने का काम आप किसी और दिन के लिए छोड़ देते हैं।
Exercism पर साइन अप कीजिए और Crystal को 26 कॉन्सेप्ट142 अभ्यास तथा असली इंसानों से मिलने वाली मेंटरिंग के साथ सीखिए और उसमें महारत हासिल कीजिए, वह भी बिल्कुल मुफ्त।