रूबी में `रेस्क्यू एक्ससेप्शन => ई` के लिए क्यों बुरा है?


894

रयान डेविस की रूबी क्विकर कहती है (बिना स्पष्टीकरण के):

अपवाद बचाव मत करो। कभी। या मैं तुम्हें चाकू मार दूंगा।

क्यों नहीं? सही बात क्या है?


35
तब आप शायद अपना लिख ​​सकते थे? :)
सर्जियो तुलन्त्सेव

65
मैं यहां हिंसा के आह्वान से बहुत असहज हूं। यह सिर्फ प्रोग्रामिंग है।
डार एग्रीशियस

1
रूबी एक्सेप्शन में इस लेख पर एक अच्छी रूबी अपवाद पदानुक्रम के साथ देखें
अतुल खंडूरी

2
क्योंकि रयान डेविस आपको चाकू मार देगा। तो बच्चे। अपवादों का बचाव कभी न करें।
मुगें

7
@DarthEgregious मैं वास्तव में नहीं बता सकता कि आप मजाक कर रहे हैं या नहीं। लेकिन मुझे लगता है कि यह प्रफुल्लित करने वाला है। (और यह स्पष्ट रूप से एक गंभीर खतरा नहीं है)। अब हर बार जब मैं अपवाद को पकड़ने के बारे में सोचता हूं, तो मुझे लगता है कि क्या यह इंटरनेट पर किसी यादृच्छिक आदमी द्वारा ठोकर खाई जाने लायक है।
स्टीव साइडर

जवाबों:


1375

टीएल; डीआर : StandardErrorसामान्य अपवाद पकड़ने के बजाय उपयोग करें । जब मूल अपवाद फिर से उठाया जाता है (उदाहरण के लिए केवल अपवाद को लॉग करने के लिए बचाया Exceptionजाता है ), तो शायद बचाव ठीक है।


Exceptionकी जड़ है रूबी के अपवाद पदानुक्रम , इसलिए जब आप rescue Exceptionआप से बचाव सब कुछ , जैसे उपवर्गों सहित SyntaxError, LoadError, और Interrupt

बचाव कार्य Interruptउपयोगकर्ता CTRLCको प्रोग्राम से बाहर निकलने से रोकता है ।

रेस्क्यूइंग SignalExceptionप्रोग्राम को संकेतों को सही ढंग से जवाब देने से रोकता है। इसे छोड़कर यह अकल्पनीय होगा kill -9

बचाव का SyntaxErrorमतलब है evalकि असफलता चुपचाप ऐसा करेगी।

इन सभी को इस कार्यक्रम को चलाकर, CTRLCया killइसे करने की कोशिश करके दिखाया जा सकता है:

loop do
  begin
    sleep 1
    eval "djsakru3924r9eiuorwju3498 += 5u84fior8u8t4ruyf8ihiure"
  rescue Exception
    puts "I refuse to fail or be stopped!"
  end
end

इससे बचना Exceptionभी डिफ़ॉल्ट नहीं है। करते हुए

begin
  # iceberg!
rescue
  # lifeboats
end

से बचाव नहीं है Exception, यह से बचाता है StandardError। आपको आम तौर पर डिफ़ॉल्ट की तुलना में कुछ अधिक विशिष्ट निर्दिष्ट करना चाहिए StandardError, लेकिन इसे संकीर्ण करने के बजाय गुंजाइश को Exception व्यापक बनाने से बचाया जा सकता है, और विनाशकारी परिणाम हो सकते हैं और बग-शिकार को बेहद मुश्किल बना सकते हैं।


यदि आपके पास ऐसी स्थिति है जहां आप बचाव करना चाहते हैं StandardErrorऔर आपको अपवाद के साथ एक चर की आवश्यकता है, तो आप इस फॉर्म का उपयोग कर सकते हैं:

begin
  # iceberg!
rescue => e
  # lifeboats
end

जो इसके बराबर है:

begin
  # iceberg!
rescue StandardError => e
  # lifeboats
end

कुछ सामान्य मामलों में से एक जहां से बचाव के Exceptionलिए यह लॉगिंग / रिपोर्टिंग उद्देश्यों के लिए है, जिस स्थिति में आपको तुरंत अपवाद को फिर से उठाना चाहिए:

begin
  # iceberg?
rescue Exception => e
  # do some logging
  raise # not enough lifeboats ;)
end

129
तो यह Throwableजावा में पकड़ने की तरह है
शाफ़्ट फ्रीक

53
यह सलाह स्वच्छ रूबी पर्यावरण के लिए अच्छी है। लेकिन दुर्भाग्य से कई रत्नों ने अपवाद बनाए हैं जो सीधे अपवाद से उतरते हैं। हमारे पर्यावरण में इनमें से 30 हैं: उदाहरण के लिए OpenID :: Server :: EncodingError, OAuth :: InvalidRequest, HTMLTokenizerSample। ये ऐसे अपवाद हैं जिन्हें आप मानक बचाव ब्लॉकों में पकड़ना चाहते हैं। दुर्भाग्य से, रूबी में कुछ भी नहीं रोकता है या यहां तक ​​कि सीधे अपवाद से विरासत में रत्नों को हतोत्साहित करता है - यहां तक ​​कि नामकरण भी अनपेक्षित है।
जोनाथन स्वार्टज

20
@JonathanSwartz फिर उन विशिष्ट उपवर्गों से बचाव, अपवाद नहीं। अधिक विशिष्ट लगभग हमेशा बेहतर और स्पष्ट होता है।
एंड्रयू मार्शल

22
@JonathanSwartz - मैं रत्न निर्माताओं को यह बदलने के लिए तैयार करूंगा कि उनका अपवाद क्या है। व्यक्तिगत रूप से, मुझे मेरे रत्न पसंद हैं, सभी अपवाद MyGemException से उतरते हैं, इसलिए आप बचाव कर सकते हैं कि यदि आप चाहते थे।
नाथन लांग

12
आप भी कर सकते हैं ADAPTER_ERRORS = [::ActiveRecord::StatementInvalid, PGError, Mysql::Error, Mysql2::Error, ::ActiveRecord::JDBCError, SQLite3::Exception]और फिरrescue *ADAPTER_ERRORS => e
j_mcnally

83

वास्तविक नियम है: दूर अपवाद न फेंके। आपके उद्धरण के लेखक की निष्पक्षता संदिग्ध है, जैसा कि इस तथ्य से स्पष्ट है कि यह समाप्त होता है

या मैं तुम्हें चाकू मार दूंगा

बेशक, इस बात से अवगत रहें कि सिग्नल (डिफ़ॉल्ट रूप से) अपवादों को फेंकते हैं, और आम तौर पर लंबे समय तक चलने वाली प्रक्रियाओं को सिग्नल के माध्यम से समाप्त किया जाता है, इसलिए अपवाद को पकड़ना और सिग्नल अपवादों को समाप्त नहीं करना आपके कार्यक्रम को रोकना बहुत कठिन बना देगा। तो यह मत करो:

#! /usr/bin/ruby

while true do
  begin
    line = STDIN.gets
    # heavy processing
  rescue Exception => e
    puts "caught exception #{e}! ohnoes!"
  end
end

नहीं, वास्तव में, यह मत करो। यह देखने के लिए कि क्या यह काम करता है, भी न चलाएं।

हालाँकि, मान लें कि आपके पास एक थ्रेडेड सर्वर है और आप चाहते हैं कि सभी अपवाद न हों:

  1. नजरअंदाज (डिफ़ॉल्ट)
  2. सर्वर बंद करो (जो आप कहते हैं तो होता है thread.abort_on_exception = true)।

फिर यह आपके कनेक्शन हैंडलिंग थ्रेड में पूरी तरह से स्वीकार्य है:

begin
  # do stuff
rescue Exception => e
  myLogger.error("uncaught #{e} exception while handling connection: #{e.message}")
    myLogger.error("Stack trace: #{backtrace.map {|l| "  #{l}\n"}.join}")
end

ऊपर रूबी के डिफ़ॉल्ट अपवाद हैंडलर की भिन्नता के लिए काम करता है, इस लाभ के साथ कि यह आपके प्रोग्राम को भी नहीं मारता है। रेल्स अपने अनुरोध हैंडलर में ऐसा करती हैं।

सिग्नल अपवाद मुख्य धागे में उठाए गए हैं। बैकग्राउंड थ्रेड्स उन्हें नहीं मिलेंगे, इसलिए उन्हें वहां पकड़ने की कोशिश करने का कोई मतलब नहीं है।

यह उत्पादन वातावरण में विशेष रूप से उपयोगी है, जहाँ आप नहीं चाहते हैं कि जब भी कुछ गलत हो , तो आप अपना कार्यक्रम बस रोक दें। तब आप अपने लॉग में स्टैक डंप ले सकते हैं और कॉल श्रृंखला के नीचे और अधिक सुंदर तरीके से विशिष्ट अपवाद से निपटने के लिए अपने कोड में जोड़ सकते हैं।

ध्यान दें कि एक और रूबी मुहावरा है जिसका प्रभाव बहुत अधिक है:

a = do_something rescue "something else"

इस पंक्ति में, यदि do_somethingकोई अपवाद उठाता है , तो उसे रूबी द्वारा पकड़ लिया जाता है, फेंक दिया जाता है और aउसे सौंपा जाता है "something else"

आम तौर पर, ऐसा मत करो, विशेष मामलों को छोड़कर जहां आप जानते हैं कि आपको चिंता करने की आवश्यकता नहीं है। एक उदाहरण:

debugger rescue nil

debuggerसमारोह अपने कोड में एक ब्रेकपाइंट सेट करने के लिए एक नहीं बल्कि अच्छा तरीका है, लेकिन अगर रेल एक डिबगर के बाहर चल रहा है, और, यह एक अपवाद को जन्म देती है। अब सैद्धांतिक रूप से आपको डिबग कोड को अपने कार्यक्रम में नहीं छोड़ना चाहिए (pff! कोई भी ऐसा नहीं करता है!) लेकिन आप इसे किसी कारण से थोड़ी देर के लिए वहीं रखना चाह सकते हैं, लेकिन लगातार अपने डिबगर को न चलाएं।

ध्यान दें:

  1. यदि आपने किसी और के प्रोग्राम को चलाया है जो सिग्नल अपवादों को पकड़ता है और उन्हें अनदेखा करता है, (ऊपर दिए गए कोड को कहें) तो:

    • लिनक्स में, एक शेल में, टाइप करें pgrep ruby, या ps | grep ruby, अपने अपमानजनक प्रोग्राम के पीआईडी ​​के लिए देखें, और फिर चलाएं kill -9 <PID>
    • विंडोज में, टास्क मैनेजर ( CTRL- SHIFT- ESC) का उपयोग करें, "प्रोसेस" टैब पर जाएं, अपनी प्रक्रिया ढूंढें, इसे राइट क्लिक करें और "एंडोर्स" चुनें।
  2. यदि आप किसी और के कार्यक्रम के साथ काम कर रहे हैं, जो भी किसी भी कारण से, इन उपेक्षा-अपवाद ब्लॉकों के साथ peppered है, तो इसे मेनलाइन के शीर्ष पर रखना एक संभव पुलिस-आउट है:

    %W/INT QUIT TERM/.each { |sig| trap sig,"SYSTEM_DEFAULT" }

    यह प्रोग्राम को तुरंत समाप्त करने के लिए सामान्य समाप्ति संकेतों का जवाब देने का कारण बनता है, अपवाद हैंडलर को दरकिनार करते हुए, जिसमें कोई सफाई नहीं है । तो यह डेटा हानि या समान हो सकता है। सावधान रहे!

  3. यदि आपको ऐसा करने की आवश्यकता है:

    begin
      do_something
    rescue Exception => e
      critical_cleanup
      raise
    end

    आप वास्तव में ऐसा कर सकते हैं:

    begin
      do_something
    ensure
      critical_cleanup
    end

    दूसरे मामले में, critical cleanupहर बार बुलाया जाएगा, चाहे एक अपवाद फेंका जाए या नहीं।


21
क्षमा करें, यह गलत है। एक सर्वर को अपवाद को कभी भी बचाव नहीं करना चाहिए और इसे लॉग इन करने के अलावा कुछ नहीं करना चाहिए । इससे सिवाय इसके अकल्पनीय हो जाएगा kill -9
जॉन

8
नोट 3 में आपके उदाहरण समतुल्य नहीं हैं, ensureचाहे कोई अपवाद उठाया गया हो या नहीं, फिर भी rescueचलेगा , जबकि अपवाद केवल तभी उठाया जाएगा जब कोई अपवाद उठाया गया था।
एंड्रयू मार्शल

1
वे / बिल्कुल / समतुल्य नहीं हैं, लेकिन मैं यह नहीं समझ सकता कि कैसे बदसूरत तरीके से समानता को व्यक्त करना है जो बदसूरत नहीं है।
माइकल स्लेड

3
पहले उदाहरण में शुरुआत / बचाव ब्लॉक के बाद बस एक और महत्वपूर्ण_क्लीनअप कॉल जोड़ें। मैं सबसे सुरुचिपूर्ण कोड से सहमत नहीं हूं, लेकिन जाहिर है कि दूसरा उदाहरण इसे करने का सुरुचिपूर्ण तरीका है, इसलिए थोड़ी सी असावधानी उदाहरण का हिस्सा है।
GTD

3
"यह भी चलाने के लिए कि क्या यह काम करता है देखने के लिए नहीं।" कोडिंग के लिए एक बुरी सलाह लगती है ... इसके विपरीत, मैं आपको इसे चलाने की सलाह दूंगा, यह देखने के लिए कि यह विफल है और अपने आप को यह समझने के लिए कि कैसे विफल रहता है, बजाय किसी और पर आंख मूंदकर विश्वास करने के बजाय। वैसे भी बढ़िया जवाब :)
15

69

टी एल; डॉ

मत करो rescue Exception => e(अपवाद नहीं है और फिर से बढ़ा) - या आप कर सकते हैं एक पुल से ड्राइव।


मान लीजिए कि आप एक कार में हैं (रूबी चल रहे हैं)। आपने हाल ही में ओवर-द-एयर अपग्रेड सिस्टम (जो उपयोग करता है eval) के साथ एक नया स्टीयरिंग व्हील स्थापित किया है , लेकिन आपको पता नहीं है कि प्रोग्रामर में से एक सिंटैक्स पर गड़बड़ कर दिया है।

आप एक पुल पर हैं, और महसूस करते हैं कि आप रेलिंग की ओर जा रहे हैं, इसलिए आप बाएं मुड़ते हैं।

def turn_left
  self.turn left:
end

उफ़! यह शायद अच्छा नहीं है ™, सौभाग्य से, रूबी एक उठाती है SyntaxError

कार को तुरंत रोकना चाहिए - सही है?

नहीं।

begin
  #...
  eval self.steering_wheel
  #...
rescue Exception => e
  self.beep
  self.log "Caught #{e}.", :warn
  self.log "Logged Error - Continuing Process.", :info
end

बीप बीप

चेतावनी: पकड़ा गया वाक्यविन्यास अपवाद।

जानकारी: त्रुटि लॉग - प्रक्रिया जारी है।

आप कुछ गलत है नोटिस, और आप आपातकालीन ब्रेक पर स्लैम ( ^C: Interrupt)

बीप बीप

चेतावनी: पकड़ा गया व्यवधान।

जानकारी: त्रुटि लॉग - प्रक्रिया जारी है।

हाँ - इससे बहुत मदद नहीं मिली। आप रेल के बहुत पास हैं, इसलिए आप कार को पार्क में रखें ( killआईएनजी:) SignalException

बीप बीप

चेतावनी: सिग्नल एक्सेप्शन अपवाद पकड़ा गया।

जानकारी: त्रुटि लॉग - प्रक्रिया जारी है।

अंतिम सेकंड में, आप कुंजियाँ खींचते हैं ( kill -9), और कार रुक जाती है, आप स्टीयरिंग व्हील में आगे की ओर खिसक जाते हैं (एयरबैग को फुलाया नहीं जा सकता क्योंकि आपने प्रोग्राम को शालीनतापूर्वक रोका नहीं था - आपने इसे समाप्त कर दिया), और कंप्यूटर अपनी कार के पिछले हिस्से में सामने की ओर खिसक लें। कोक का आधा भरा हुआ कागज़ कागजों पर फैल सकता है। पीठ में किराने का सामान कुचल दिया जाता है, और अधिकांश अंडे की जर्दी और दूध में ढंके होते हैं। कार को गंभीर मरम्मत और सफाई की आवश्यकता है। (डेटा हानि)

उम्मीद है कि आपके पास बीमा (बैकअप) है। अरे हाँ - क्योंकि एयरबैग फुलाया नहीं गया, आपको शायद चोट लगी है (निकाल दिया जा रहा है, आदि)।


लेकिन रुकें! वहाँ हैअधिकआप क्यों उपयोग करना चाहते हैं कारण rescue Exception => e!

मान लीजिए कि आप वह कार हैं, और आप यह सुनिश्चित करना चाहते हैं कि अगर कार अपनी सुरक्षित गति को पार कर रही है तो एयरबैग फुलाए।

 begin 
    # do driving stuff
 rescue Exception => e
    self.airbags.inflate if self.exceeding_safe_stopping_momentum?
    raise
 end

यहां नियम का अपवाद है: आप Exception केवल तभी पकड़ सकते हैं जब आप अपवाद को फिर से बढ़ाएं । इसलिए, एक बेहतर नियम यह है कि कभी न निगलें Exception, और हमेशा त्रुटि को फिर से उठाएं।

लेकिन बचाव को जोड़ना रूबी जैसी भाषा में भूलना आसान है, और किसी मुद्दे को फिर से उठाने से ठीक पहले एक बचाव वक्तव्य देना थोड़ा गैर-डीआरवाई लगता है। और आप कथन को भूलना नहीं चाहते raise। और यदि आप करते हैं, तो सौभाग्य उस त्रुटि को खोजने की कोशिश कर रहा है।

शुक्र है, रूबी कमाल की है, आप बस ensureकीवर्ड का उपयोग कर सकते हैं , जो यह सुनिश्चित करता है कि कोड चलता है। ensureकीवर्ड कोई बात नहीं क्या कोड चलेंगे - अगर एक अपवाद फेंक दिया जाता है, अगर एक नहीं है, एकमात्र अपवाद अगर किया जा रहा है दुनिया समाप्त होता है (या अन्य संभावना नहीं घटनाओं)।

 begin 
    # do driving stuff
 ensure
    self.airbags.inflate if self.exceeding_safe_stopping_momentum?
 end

बूम! और उस कोड को वैसे भी चलाना चाहिए। एकमात्र कारण जो आपको उपयोग करना चाहिए rescue Exception => e, यदि आपको अपवाद तक पहुंच की आवश्यकता है, या यदि आप केवल एक अपवाद पर कोड चलाना चाहते हैं। और त्रुटि को फिर से उठाना याद रखें। हर बार।

नोट: जैसा कि @Niall ने बताया है, हमेशा रन सुनिश्चित करें । यह अच्छा है क्योंकि कभी-कभी आपका कार्यक्रम आपसे झूठ बोल सकता है और अपवादों को नहीं फेंक सकता, तब भी जब मुद्दे होते हैं। महत्वपूर्ण कार्यों के साथ, एयरबैग को फुलाए जाने की तरह, आपको यह सुनिश्चित करने की आवश्यकता है कि ऐसा कुछ भी न हो। इस वजह से, हर बार जाँच करना बंद कर दिया जाता है कि कार एक अपवाद है या नहीं, एक अच्छा विचार है। भले ही एयरबैग को फुलाया जाना अधिकांश प्रोग्रामिंग संदर्भों में एक असामान्य काम है, लेकिन यह वास्तव में अधिकांश सफाई कार्यों के साथ बहुत आम है।


12
हा हा हा हा! यह एक बेहतरीन जवाब है। मैं हैरान हूं कि किसी ने कोई टिप्पणी नहीं की। आप एक स्पष्ट परिदृश्य देते हैं जो पूरी बात को वास्तव में समझने योग्य बनाता है। चीयर्स! :-)
जेम्स मिलानी

@JamesMilani धन्यवाद!
बेन ऑबिन

3
+ + इस उत्तर के लिए। काश मैं एक से अधिक बार उत्थान कर पाता! Ave
इंजीनियरडव

1
आपके उत्तर का आनंद लिया!
अतुल वैभव

3
यह उत्तर पूरी तरह से समझने योग्य और सही स्वीकार किए जाने के 4 साल बाद आया, और इसे एक बेतुके परिदृश्य के साथ फिर से समझाया गया जिसे यथार्थवादी की तुलना में अधिक मनोरंजक बनाया गया था। बुज़ेकिल होने के लिए क्षमा करें, लेकिन यह लाल रंग नहीं है - जवाब देने के लिए अधिक महत्वपूर्ण है कि यह रसीला हो और मजाकिया से सही हो। इसके अलावा, ensureएक विकल्प के रूप में के बारे में rescue Exceptionभ्रामक है - उदाहरण का अर्थ है कि वे समतुल्य हैं, लेकिन जैसा कि कहा गया ensureहै कि एक अपवाद है या नहीं, इसलिए अब आपके एयरबैग फुलाएंगे क्योंकि आप 5mph से अधिक चले गए, हालांकि कुछ भी गलत नहीं हुआ।
निआल

47

क्योंकि यह सभी अपवादों को पकड़ लेता है। यह संभावना नहीं है कि आपका कार्यक्रम उनमें से किसी से भी पुनर्प्राप्त कर सकता है।

आपको केवल उन अपवादों को संभालना चाहिए जिन्हें आप जानते हैं कि इससे कैसे उबरना है। यदि आप एक विशेष प्रकार के अपवाद का अनुमान नहीं लगाते हैं, तो इसे संभालें नहीं, ज़ोर से क्रैश करें (लॉग को विवरण लिखें), फिर लॉग और फिक्स कोड का निदान करें।

अपवादों को निगलना बुरा है, ऐसा मत करो।


10

यह नियम का एक विशिष्ट मामला है कि आपको किसी भी अपवाद को नहीं पकड़ना चाहिए जिसे आप नहीं जानते कि कैसे संभालना है। यदि आप नहीं जानते कि इसे कैसे संभालना है, तो सिस्टम के कुछ अन्य भाग को पकड़ना और उसे संभालना बेहतर होता है।


0

मैं सिर्फ हनीबैगर पर इसके बारे में एक महान ब्लॉग पोस्ट पढ़ता हूं

रूबी का अपवाद बनाम स्टैंडर्डर्रर: क्या अंतर है?

आपको अपवाद क्यों नहीं करना चाहिए

एक्सेप्शन को रेस्क्यू करने में समस्या यह है कि यह वास्तव में एक्सेप्शन से निकलने वाले हर अपवाद को बचाता है। जो .... वो सब!

यह एक समस्या है क्योंकि कुछ अपवाद हैं जो रूबी द्वारा आंतरिक रूप से उपयोग किए जाते हैं। उन्हें आपके ऐप से कोई लेना-देना नहीं है, और उन्हें निगलने से बुरा काम होगा।

यहाँ कुछ बड़े हैं:

  • SignalException :: Interrupt - यदि आप इसे बचाव करते हैं, तो आप नियंत्रण-सी मारकर अपने ऐप से बाहर नहीं निकल सकते हैं।

  • ScriptError :: SyntaxError - वाक्यविन्यास त्रुटियों को निगलने का अर्थ है कि चीजें ("कुछ भूल गई") चुपचाप विफल हो जाएंगी।

  • NoMemoryError - पता है कि क्या होता है जब आपका प्रोग्राम सभी RAM का उपयोग करने के बाद भी चलता रहता है? न ही मैं।

begin
  do_something()
rescue Exception => e
  # Don't do this. This will swallow every single exception. Nothing gets past it. 
end

मैं अनुमान लगा रहा हूं कि आप वास्तव में इनमें से किसी भी सिस्टम-स्तर के अपवादों को निगलना नहीं चाहते हैं। आप केवल अपने सभी एप्लिकेशन स्तर की त्रुटियों को पकड़ना चाहते हैं। अपवाद आपके कोड का कारण बने।

सौभाग्य से, इसका एक आसान तरीका है।

हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.