ऐपसिग्नल पर हम रूबी एप्लिकेशन के लिए त्रुटि ट्रैकिंग प्रदान करते हैं। ऐसा करने के लिए, हम उन सभी अपवादों को कैप्चर करते हैं जो एप्लिकेशन हम पर फेंकते हैं और जैसे ही वे होते हैं डेवलपर्स को सूचित करते हैं।
अपवाद प्रबंधन को सही करना मुश्किल हो सकता है। इस लेख में हम बताएंगे कि यह कैसे काम करता है, खराब प्रबंधन क्या समस्याएं पैदा कर सकता है और अपवादों को सही तरीके से कैसे बचाया जाए।
अपवादों को बचाना
रूबी में अपवादों को बचाकर आप अपने एप्लिकेशन को उस क्षण क्रैश होने से रोक सकते हैं जब कुछ गलत होता है। begin .. rescue ब्लॉक के साथ आप अपने एप्लिकेशन के लिए एक वैकल्पिक पथ निर्दिष्ट कर सकते हैं जब कोई त्रुटि होती है।
begin File.read "config.yml" rescue puts "No config file found. Using defaults." end
यह निर्दिष्ट करना भी संभव है कि किन अपवादों को बचाया जाना चाहिए। जब एक अपवाद वर्ग निर्दिष्ट किया जाता है, तो इस अपवाद के सभी उपवर्ग भी कैप्चर किए जाएंगे।
begin File.read "config.yml" rescue SystemCallError => e puts e.class # => Errno::ENOENT puts e.class.superclass # => SystemCallError puts e.class.superclass.superclass # => StandardError end
उपरोक्त उदाहरण में आप देख सकते हैं कि अपवाद Errno::ENOENT तब पकड़ा जाता है जब इसका पैरेंट SystemCallError बचाया जा रहा है।
अपवाद श्रृंखला में बहुत ऊपर बचाना
अपवाद श्रृंखला में बहुत ऊपर अपवादों को बचाना महत्वपूर्ण नहीं है। जब आप ऐसा करते हैं, तो सभी उपवर्गीय अपवाद भी पकड़े जाएंगे, जिससे rescue ब्लॉक का कैप्चर बहुत सामान्य हो जाता है।
यहां एक प्रोग्राम है जो प्रोग्राम को पास किए गए आर्गुमेंट के आधार पर एक कॉन्फिग फाइल पढ़ता है।
# $ ruby example.rb config.yml
def config_file
ARGV.firs # यहां टाइपो है, हमारा मतलब `ARGV.first` था।
end
begin File.read config_file rescue puts "Couldn't read the config file" end
त्रुटि संदेश कहता है कि यह कॉन्फिग फाइल नहीं पढ़ सका, लेकिन वास्तविक समस्या कोड में टाइपो थी।
begin File.read config_file rescue => e puts e.inspect end
#<NoMethodError: undefined method `firs' for []:Array>
begin .. rescue ब्लॉक द्वारा डिफ़ॉल्ट अपवाद वर्ग पकड़ा जाता है StandardError। यदि हम कोई विशिष्ट वर्ग पास नहीं करते, तो रूबी StandardError और सभी उपवर्गीय त्रुटियों को बचाएगा। NoMethodError इन त्रुटियों में से एक है।
एक विशिष्ट अपवाद वर्ग को बचाने से असंबंधित त्रुटियों को गलती से विफलता स्थिति को प्रेरित करने से रोकने में मदद मिलेगी। यह अधिक विशिष्ट कस्टम त्रुटि संदेशों की भी अनुमति देता है जो अंतिम उपयोगकर्ता के लिए अधिक सहायक होते हैं।
config_file = "config.yml"
begin File.read config_file rescue Errno::ENOENT => e puts "File or directory #{config_file} doesn't exist." rescue Errno::EACCES => e puts "Can't read from #{config_file}. No permission." end
Exception बचाना
अपवाद श्रृंखला में ऊपर बचाना अभी भी आकर्षक हो सकता है। एप्लिकेशन द्वारा उठाए जा सकने वाले सभी त्रुटियों को बचाने से यह क्रैश होने से बच जाएगा। (100% अपटाइम यहां हम आते हैं!) हालांकि, यह बहुत सारी समस्याएं पैदा कर सकता है।
Exception क्लास रूबी में मुख्य अपवाद वर्ग है। अन्य सभी अपवाद इस वर्ग के उपवर्ग हैं; यदि Exception बचाया जाता है तो सभी त्रुटियां पकड़ी जाएंगी।
दो अपवाद जिन्हें अधिकांश एप्लिकेशन बचाना नहीं चाहेंगे वे हैं SignalException और SystemExit।
SignalException का उपयोग तब किया जाता है जब कोई बाहरी स्रोत एप्लिकेशन को रुकने के लिए कह रहा है। यह ऑपरेटिंग सिस्टम हो सकता है जब वह बंद होना चाहता है, या एक सिस्टम एडमिनिस्ट्रेटर जो एप्लिकेशन को रोकना चाहता है। उदाहरण
SystemExit का उपयोग तब किया जाता है जब रूबी एप्लिकेशन से
exitकॉल किया जा रहा है। जब यह उठाया जाता है तो डेवलपर एप्लिकेशन को रोकना चाहता है। उदाहरण
यदि हम Exception को बचाते हैं और ये अपवाद उठाए जाते हैं जब एप्लिकेशन वर्तमान में
begin ... rescue ... endब्लॉक चला रहा है तो यह बाहर नहीं निकल सकता।
सामान्य स्थितियों में Exception को बचाना आम तौर पर एक बुरा विचार है। Exception को बचाते समय, आप SignalException और SystemExit को कार्य करने से रोकेंगे, बल्कि LoadError, SyntaxError और NoMemoryError, कुछ नाम रखने के लिए। अधिक विशिष्ट अपवादों को बचाना बेहतर है।
परीक्षणों में विफलताएं
जब Exception को बचाया जाता है, rescue Exception => e का उपयोग करके, आपके एप्लिकेशन के अलावा अन्य चीजें टूट सकती हैं। टेस्ट सूट वास्तव में कुछ त्रुटियों को छुपा सकता है।
Minitest और RSpec में विफल होने वाले दावे आपको विफल दावे के बारे में सूचित करने के लिए एक अपवाद उठाएंगे, जिससे टेस्ट विफल हो जाएगा। जब वे ऐसा करते हैं, तो वे अपने स्वयं के कस्टम अपवाद उठाते हैं, जो Exception से उपवर्गित होते हैं।
यदि Exception को टेस्ट में या एप्लिकेशन कोड में बचाया जाता है, तो यह दावा विफलता को चुप करा सकता है।
# RSpec उदाहरण
def foo(bar)
bar.baz
rescue Exception => e
puts "This test should actually fail"
# Failure/Error: bar.baz
# <Double (anonymous)> received unexpected message :baz with (no args)
end
describe "#foo" do
it "hides an 'unexpected message' exception" do
bar = double(to_s: "")
foo(bar)
end
end
अपवादों की अपेक्षा करना
कुछ कोड अपवाद उठाने के लिए होता है। टेस्ट सूट में अपवाद को चुप कराना संभव है ताकि जब वे उठाए जाएं तो टेस्ट विफल न हो।
def foo
raise RuntimeError, "something went wrong"
end
foo rescue RuntimeError
हालांकि, यह टेस्ट नहीं करता कि अपवाद उठाया गया था या नहीं। जब अपवाद नहीं उठाया जाता, तो आपका टेस्ट यह बताने में सक्षम नहीं होगा कि व्यवहार अभी भी सही है या नहीं।
यह दावा करना संभव है कि अपवाद उठाया गया है, और यदि नहीं, तो कौन सा अपवाद था।
# expecting_exceptions_spec.rb
# RSpec उदाहरण
def foo
raise NotImplementedError, "foo method not implemented"
end
describe "#foo" do
it "raises a RuntimeError" do
expect { foo }.to raise_error(RuntimeError)
end
end
1) #foo raises a RuntimeError
Failure/Error: expect { foo }.to raise_error(RuntimeError)
expected RuntimeError, got #<NotImplementedError: foo method not implemented>
with backtrace:
# ./expecting_exceptions_spec.rb:4:in `foo'
# ./expecting_exceptions_spec.rb:9:in `block (3 levels) in <top (required)>'
# ./expecting_exceptions_spec.rb:9:in `block (2 levels) in <top (required)>'
# ./expecting_exceptions_spec.rb:9:in `block (2 levels) in <top (required)>'
Exception को फिर से उठाना
एप्लिकेशन को केवल तभी अपवाद श्रृंखला में Exception क्लास जितना ऊपर कैप्चर करना चाहिए जब इसके लिए बहुत अच्छा कारण हो। उदाहरण के लिए, जब कोड के ब्लॉक से बाहर निकलने से पहले कुछ सफाई शामिल हो, जैसे अस्थायी फाइलें हटाना जिन्हें वास्तव में हटाने की आवश्यकता हो।
जब आपको बिल्कुल Exception को बचाना ही हो तो एक सिफारिश है, इसे संभालने के बाद फिर से उठाएं। इस तरह रूबी अपवाद प्रबंधन बाद में प्रोसेस के भाग्य का फैसला कर सकता है।
File.open("/tmp/my_app.status", "w") { |f| "running" }
begin
foo
rescue Exception => e
Appsignal.add_error e
File.open("/tmp/my_app.status", "w") { |f| "stopped" }
raise e
end
अनिश्चित क्या बचाएं?
जैसा कि पहले उल्लेख किया गया है, यह अच्छा है कि किन त्रुटियों को बचाना है उसमें विशिष्ट रहें।
जब आप अनिश्चित हों कि कोई ऑपरेशन कौन से अपवाद उठा सकता है, तो StandardError को बचाना शुरू करने के लिए एक अच्छी जगह हो सकती है। अपने कोड को विभिन्न परिदृश्यों में चलाएं और देखें कि यह कौन से अपवाद उठाता है।
begin File.open('/tmp/appsignal.log', 'a') { |f| f.write "Starting AppSignal" } rescue => e puts e.inspect end
#<Errno::EACCES: Permission denied @ rb_sysopen - /tmp/appsignal.log>
हर बार जब आप एक नए अपवाद का सामना करते हैं, तो उन अपवादों या उनके प्रासंगिक पैरेंट क्लास के लिए विशिष्ट rescue केस जोड़ें। बहुत सारे अपवादों को बचाने के बजाय बचाने के लिए क्या विशिष्ट होना बेहतर है।
begin
file = '/tmp/appsignal.log'
File.open(file, 'a') { |f| f.write("AppSignal started!") }
rescue Errno::ENOENT => e
puts "File or directory #{file} doesn't exist."
rescue Errno::EACCES => e
puts "Cannot write to #{file}. No permissions."
end
# या, पैरेंट एरर क्लास का उपयोग करके
begin
file = '/tmp/appsignal.log'
File.open(file, 'a')
rescue SystemCallError => e
puts "Error while writing to file #{file}."
puts e
end
यह रूबी में अपवाद प्रबंधन पर हमारे प्राइमर का समापन करता है। @AppSignal पर हमें बताएं यदि आप अधिक जानना चाहते हैं, या कोई विशिष्ट प्रश्न है। यदि आप अपने ऐप में अपवाद कहां और कितनी बार उठाए जाते हैं, इसमें बेहतर अंतर्दृष्टि प्राप्त करना चाहते हैं, तो ऐपसिग्नल को आज़माएं।