क्या C ++ बुरे अभ्यास में Assert () का उपयोग कर रहा है?


93

मैं रिलीज सी बिल्ड्स के प्रदर्शन को प्रभावित किए बिना डिबगिंग को आसान बनाने के लिए अपने सी ++ कोड में बहुत सारे दावे जोड़ देता हूं। अब, assertसी ++ तंत्रों को ध्यान में रखे बिना शुद्ध सी मैक्रो है।

दूसरी ओर सी ++ परिभाषित करता है std::logic_error, जिसका मतलब उन मामलों में है जहां कार्यक्रम के तर्क में त्रुटि है (इसलिए नाम)। एक उदाहरण फेंकना सिर्फ सही हो सकता है, और अधिक C ++ ish विकल्प assert

समस्या यह है कि assertऔर abortदोनों विध्वंसक को बुलाए बिना कार्यक्रम को तुरंत समाप्त कर देते हैं, इसलिए सफाई को छोड़ देते हैं, जबकि एक अपवाद को मैन्युअल रूप से फेंकने से अनावश्यक रनटाइम लागत में वृद्धि होती है। इसके चारों ओर एक तरह से एक स्वयं के स्थूल मैक्रो का निर्माण होगा SAFE_ASSERT, जो सी समकक्ष की तरह काम करता है, लेकिन विफलता पर एक अपवाद फेंकता है।

मैं इस समस्या पर तीन राय सोच सकता हूं:

  • C के मुखर से चिपके रहें। चूंकि कार्यक्रम तुरंत समाप्त हो गया है, इससे कोई फर्क नहीं पड़ता कि परिवर्तन सही ढंग से अनियंत्रित हैं या नहीं। इसके अलावा, #defineC ++ में s का उपयोग करना उतना ही बुरा है।
  • एक अपवाद को फेंक दें और इसे मुख्य () में पकड़ लें । प्रोग्राम के किसी भी राज्य में विध्वंसक को छोड़ने के लिए कोडिंग करना बुरी प्रथा है और इसे हर कीमत पर टाला जाना चाहिए, और इसलिए कॉल को समाप्त किया जाना चाहिए ()। यदि अपवादों को फेंक दिया जाता है, तो उन्हें पकड़ा जाना चाहिए।
  • एक अपवाद फेंकें और इसे कार्यक्रम को समाप्त करने दें। एक कार्यक्रम को समाप्त करने वाला एक अपवाद ठीक है, और इसके कारण NDEBUG, रिलीज बिल्ड में ऐसा कभी नहीं होगा। पकड़ना अनावश्यक है और आंतरिक कोड के कार्यान्वयन के विवरण को उजागर करता है main()

क्या इस समस्या का कोई निश्चित जवाब है? कोई पेशेवर संदर्भ?

संपादित: लंघन विध्वंसक, निश्चित रूप से, कोई अपरिभाषित व्यवहार नहीं है।


22
नहीं, वास्तव में, logic_errorतर्क त्रुटि है। कार्यक्रम के तर्क में त्रुटि को बग कहा जाता है। आप अपवादों को फेंककर बग को हल नहीं करते हैं।
आर। मार्टिनो फर्नांडिस

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

5
सुनिश्चित करें कि आप इसका उपयोग करें static_assertजहां यह आपके पास उपलब्ध हो तो उचित है।
फ्लेक्सो

4
@ मैं नहीं देखता कि यह कैसे मदद करता है। आप फेंक देंगे std::bug?
आर। मार्टिनो फर्नांडिस

3
@ प्रवृत्ति: ऐसा मत करो। अपवाद डिबगिंग के लिए नहीं हैं। कोई व्यक्ति अपवाद को पकड़ सकता है। कॉल करते समय यूबी के बारे में चिंता करने की आवश्यकता नहीं है std::abort(); यह सिर्फ एक संकेत देगा जो प्रक्रिया को समाप्त करने का कारण बनता है।
केरेक एसबी

जवाबों:


73

C ++ कोड में दावे पूरी तरह से उपयुक्त हैं। अपवाद और अन्य त्रुटि से निपटने तंत्र वास्तव में दावे के रूप में एक ही बात के लिए इरादा नहीं कर रहे हैं।

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

दावे की जाँच ऐसी चीजों के लिए होती है, जो एपीआई की आवश्यकताओं की पूर्ति तब होती हैं जब एपीआई की सामान्य रूप से जाँच नहीं की जाती है, या चीजों की जाँच के लिए डेवलपर का मानना ​​है कि वह निर्माण की गारंटी देता है। उदाहरण के लिए यदि एल्गोरिथ्म के लिए हल किए गए इनपुट की आवश्यकता होती है, तो आप सामान्य रूप से इसकी जांच नहीं करेंगे, लेकिन आपके पास इसे जांचने के लिए जोर हो सकता है, ताकि डिबग उस तरह के बग को ध्वज बनाता है। एक जोर हमेशा एक गलत तरीके से ऑपरेटिंग प्रोग्राम को इंगित करना चाहिए।


यदि आप एक ऐसा कार्यक्रम लिख रहे हैं जहाँ अशुद्ध शटडाउन समस्या पैदा कर सकता है तो आप दावे से बचना चाह सकते हैं। सी ++ भाषा के संदर्भ में सख्ती से अपरिभाषित व्यवहार यहां ऐसी समस्या के रूप में योग्य नहीं है, क्योंकि जोर से मारना संभवतः पहले से ही अपरिभाषित व्यवहार का परिणाम है, या कुछ अन्य आवश्यकता का उल्लंघन है जो कुछ सफाई को ठीक से काम करने से रोक सकता है।

इसके अलावा, यदि आप अपवाद के संदर्भ में अभिक्रियाओं को लागू करते हैं तो यह संभावित रूप से पकड़ा जा सकता है और 'संभाला' जा सकता है, भले ही यह दावे के बहुत ही विपरीत हो।


1
मुझे पूरी तरह से यकीन नहीं है कि यह विशेष रूप से उत्तर में कहा गया था, इसलिए मैं इसे यहां बताऊंगा: आपको उपयोगकर्ता इनपुट से संबंधित किसी भी चीज के लिए एक दावे का उपयोग नहीं करना चाहिए जिसे कोड लिखने के समय निर्धारित नहीं किया जा सकता है। यदि कोई उपयोगकर्ता आपके कोड के 3बजाय गुजरता है 1, तो सामान्य रूप से इसे जोर नहीं देना चाहिए। दावे केवल प्रोग्रामर त्रुटि हैं, न कि लाइब्रेरी या एप्लिकेशन त्रुटि के उपयोगकर्ता।
एसएस ऐनी

101
  • डिबगिंग के लिए दावे हैं । आपके शिप किए गए कोड के उपयोगकर्ता को उन्हें कभी नहीं देखना चाहिए। यदि एक जोर मारा जाता है, तो आपके कोड को ठीक करना होगा।

  • अपवाद असाधारण परिस्थितियों के लिए हैं । यदि एक का सामना किया जाता है, तो उपयोगकर्ता वह नहीं कर पाएगा जो वह चाहती है, लेकिन कहीं और फिर से शुरू करने में सक्षम हो सकती है।

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


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

5
@ स्लैशमाईस: भावना प्रशंसनीय है, लेकिन जब तक आप सही, बग-मुक्त कोड शिपिंग नहीं करते, मुझे एक आश्वासन (यहां तक ​​कि उपयोगकर्ता को क्रैश करने वाला) अपरिभाषित व्यवहार के लिए बेहतर लगता है। कीड़े जटिल प्रणालियों में होते हैं, और एक जोर के साथ आपके पास इसे देखने और निदान करने का एक तरीका है जहां यह होता है।
केरेक एसबी

@KerrekSB मैं एक अपवाद पर एक अपवाद का उपयोग करना चाहते हैं। कम से कम कोड में असफल शाखा को त्यागने और कुछ और उपयोगी करने का मौका है। बहुत कम से कम, यदि आप RAII का उपयोग कर रहे हैं, तो आपके सभी बफ़र्स फ़ाइलों को खोलने के लिए ठीक से फ्लश हो जाएंगे।
daemonspring

14

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


मेरे पास एक समान दृष्टिकोण है। मैं तर्क के लिए मुखरता का उपयोग करता हूं जो शायद स्थानीय रूप से सही होना चाहिए (जैसे लूप इनवेरिएंट्स)। अपवाद ऐसे हैं, जहां एक तर्क त्रुटि को गैर-क्षेत्रीय (बाहरी) स्थिति द्वारा कोड पर मजबूर किया गया था ।
जुलाब

यदि एक जोरदार विफल रहता है, तो इसका मतलब है कि कार्यक्रम के भाग का तर्क टूट गया है। एक असफल दावा जरूरी नहीं है कि कुछ भी पूरा नहीं किया जा सकता है। एक टूटे हुए प्लगिन को संभवत: पूरे वर्ड प्रोसेसर को निरस्त नहीं करना चाहिए।
daemonspring

13

सहयोगी गर्भपात () के कारण विनाशकारी नहीं चलना अपरिभाषित व्यवहार नहीं है!

यदि ऐसा होता, तो इसे कॉल करने के लिए अपरिभाषित व्यवहार std::terminate()भी होता, और इसलिए इसे प्रदान करने में क्या बात होगी?

assert() सी ++ में बस के रूप में उपयोगी है। सी। त्रुटि से निपटने के लिए नहीं हैं, वे कार्यक्रम को तुरंत समाप्त करने के लिए हैं।


1
मैं कहूंगा abort()कि कार्यक्रम को तुरंत रद्द करने के लिए है। आप सही कह रहे हैं कि दावे हालांकि त्रुटि से निपटने के लिए नहीं हैं, फिर भी जोर देकर गर्भपात को संभालने की कोशिश करते हैं। क्या आपको इसके बजाय एक अपवाद नहीं फेंकना चाहिए और कॉलर को त्रुटि को संभालने दें यदि यह हो सकता है? आखिरकार, यह निर्धारित करने के लिए कॉल करने वाला बेहतर स्थिति में है कि क्या एक फ़ंक्शन की विफलता इसे और कुछ करने के लायक नहीं बनाती है। हो सकता है कि फोन करने वाला तीन असंबंधित चीजों को करने की कोशिश कर रहा हो और अभी भी अन्य दो कामों को पूरा कर सके और सिर्फ इस एक को छोड़ दे।
डेमोंसप्रिंग

और assertकॉल करने के लिए परिभाषित किया गया है abort(जब स्थिति झूठी है)। अपवादों को फेंकने के लिए, नहीं, यह हमेशा उचित नहीं है। कुछ चीजों को कॉलर द्वारा नियंत्रित नहीं किया जा सकता है। कॉलर यह निर्धारित नहीं कर सकता है कि तृतीय-पक्ष लाइब्रेरी फ़ंक्शन में लॉजिक बग पुनर्प्राप्त करने योग्य है, या यदि दूषित डेटा ठीक किया जा सकता है।
जोनाथन वेकली

6

IMHO, दावे शर्तों की जाँच के लिए है कि यदि उल्लंघन किया जाता है, तो बाकी सब बकवास करें। और इसलिए आप उनसे उबर नहीं सकते हैं, बल्कि उबरना अप्रासंगिक है।

मैं उन्हें 2 श्रेणियों में समूहित करूंगा:

  • डेवलपर पाप (उदाहरण के लिए एक संभावित फ़ंक्शन जो नकारात्मक मान देता है):

फ्लोट संभावना () {रिटर्न -1.0; }

मुखर (संभावना) (=> 0.0)

  • मशीन टूट गई है (जैसे कि मशीन जो आपका प्रोग्राम चलाती है वह बहुत गलत है):

int x = 1;

मुखर (x> 0);

ये दोनों तुच्छ उदाहरण हैं लेकिन वास्तविकता से बहुत दूर नहीं हैं। उदाहरण के लिए, भोले एल्गोरिदम के बारे में सोचें जो वैक्टर के साथ उपयोग करने के लिए नकारात्मक सूचकांक लौटाते हैं। या कस्टम हार्डवेयर में एम्बेडेड प्रोग्राम। या बल्कि क्योंकि श * टी होता है

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


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