कोड लिखने के चरण में त्रुटियों की संभावना को कैसे कम करें। नोट N3

पीवीएस-स्टूडियो वीईएस क्यूटी
यह तीसरा लेख है जहां मैं प्रोग्रामिंग चाल की एक नई जोड़ी के बारे में बात करना चाहता हूं जो कोड को सरल और अधिक विश्वसनीय बनाने में मदद करेगा। पिछले दो नोट यहां [1] और यहां [2] मिल सकते हैं। इस बार के उदाहरण Qt प्रोजेक्ट से लिए जाएंगे।

परिचय


प्रोजेक्ट Qt 4.7.3। मुझे एक कारण से अध्ययन करना पड़ा। पीवीएस-स्टूडियो के उपयोगकर्ताओं ने देखा कि क्यूटी लाइब्रेरी के आधार पर बनाई गई परियोजनाओं की जांच करने पर विश्लेषण किसी तरह कमजोर है। यह आश्चर्य की बात नहीं है। स्थैतिक विश्लेषण आपको इस तथ्य के कारण त्रुटियों को खोजने की अनुमति देता है कि यह संकलक की तुलना में उच्च स्तर पर कोड को देखता है। इसलिए, उसे कोड के कुछ पैटर्न और विभिन्न पुस्तकालयों के कार्यों को जानना चाहिए। अन्यथा, वह कई अद्भुत गलतियों से गुजरेगा। मैं एक उदाहरण के साथ समझाऊंगा:
if (strcmp(My_Str_A, My_Str_A) == 0) 

यह स्ट्रिंग से खुद की तुलना करने का कोई मतलब नहीं है। लेकिन कंपाइलर चुप है। वह strcmp () फ़ंक्शन के सार के बारे में नहीं सोचता है। उसकी काफी चिंताएँ हैं। लेकिन स्थैतिक विश्लेषणकर्ताओं को संदेह हो सकता है कि कुछ गलत था। Qt की अपनी तरह का स्ट्रिंग तुलनात्मक कार्य है - qstrcmp ()। और, तदनुसार, विश्लेषक को निम्नलिखित पंक्ति पर ध्यान देने के लिए प्रशिक्षित करने की आवश्यकता है:
 if (qstrcmp(My_Str_A, My_Str_A) == 0) 

क्यूटी लाइब्रेरी को माहिर करना और विशेष चेक बनाना एक बड़ा और व्यवस्थित काम है। और इस काम की शुरुआत खुद पुस्तकालय की जाँच थी।

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

1. प्रक्रिया चर उसी क्रम में हैं जब वे घोषित किए जाते हैं


Qt लाइब्रेरी कोड बहुत उच्च गुणवत्ता वाला और व्यावहारिक रूप से त्रुटियों से मुक्त है। लेकिन इसने बड़ी संख्या में अत्यधिक आरंभीकरण, अत्यधिक तुलना या चर के मूल्यों की अत्यधिक नकल का खुलासा किया।

यहाँ स्पष्टता के लिए कुछ उदाहरण दिए गए हैं:
 QWidget *WidgetFactory::createWidget(...) { ... } else if (widgetName == m_strings.m_qDockWidget) { <<<=== w = new QDesignerDockWidget(parentWidget); } else if (widgetName == m_strings.m_qMenuBar) { w = new QDesignerMenuBar(parentWidget); } else if (widgetName == m_strings.m_qMenu) { w = new QDesignerMenu(parentWidget); } else if (widgetName == m_strings.m_spacer) { w = new Spacer(parentWidget); } else if (widgetName == m_strings.m_qDockWidget) { <<<=== w = new QDesignerDockWidget(parentWidget); ... } 

यहां एक ही तुलना को दो बार दोहराया गया है। यह कोई गलती नहीं है, बल्कि पूरी तरह से अनावश्यक कोड है। इसी तरह का एक और उदाहरण:
 void QXmlStreamReaderPrivate::init() { tos = 0; <<<=== scanDtd = false; token = -1; token_char = 0; isEmptyElement = false; isWhitespace = true; isCDATA = false; standalone = false; tos = 0; <<<=== ... } 

फिर से, एक गलती नहीं है, लेकिन चर के पूरी तरह से अनावश्यक दोहरी आरंभीकरण। और मैंने बहुत सी समान डुप्लिकेट कार्रवाइयों को गिना। वे तुलना, असाइनमेंट, इनिशियलाइज़ेशन की लंबी सूची के कारण उत्पन्न होते हैं। यह बस दिखाई नहीं देता है कि चर पहले से ही संसाधित हो रहा है, यही कारण है कि अनावश्यक संचालन दिखाई देते हैं। मैं इस तरह के नकली कार्यों के तीन अप्रिय परिणामों का नाम दे सकता हूं:
  1. डुप्लिकेट कोड की लंबाई बढ़ाता है। और कोड जितना लंबा होगा, एक और डुप्लिकेट जोड़ना उतना ही आसान होगा।
  2. यदि हम प्रोग्राम लॉजिक को बदलना चाहते हैं और एक चेक या एक असाइनमेंट को हटाना चाहते हैं, तो इस एक्शन की डुप्लिकेट आपको कई घंटों का रोमांचक डिबगिंग दे सकती है। अपने आप को कल्पना कीजिए, आप "tos = 1" लिखते हैं (पहला उदाहरण देखें), और फिर कार्यक्रम के एक अन्य भाग में आप आश्चर्यचकित होते हैं कि "टॉस" अभी भी शून्य क्यों है।
  3. गति धीमी करें। लगभग हमेशा, ऐसी स्थितियों में उनकी उपेक्षा की जा सकती है, लेकिन यह अभी भी मौजूद है।

मुझे आशा है कि मैंने आश्वस्त किया कि हमारे कोड में डुप्लिकेट के लिए कोई जगह नहीं है। उनसे कैसे निपटें? आमतौर पर, ऐसी इनिशियलाइज़ेशन / तुलनाएँ ब्लॉक में जाती हैं। और चर का एक ही ब्लॉक है। कोड लिखना तर्कसंगत है, ताकि चर घोषणाओं का क्रम और उनके साथ काम करने की प्रक्रिया मेल खाती हो। यहाँ एक अच्छा कोड नहीं तो उदाहरण है:
 struct T { int x, y, z; float m; int q, w, e, r, t; } A; ... Am = 0.0; Aq = 0; Ax = 0; Ay = 0; Az = 0; Aq = 0; Aw = 0; Ar = 1; Ae = 1; At = 1; 

स्वाभाविक रूप से, यह एक योजनाबद्ध उदाहरण है। मुद्दा यह है कि जब आरंभीकरण अनुक्रमिक नहीं है, तो दो समान रेखाएं लिखना बहुत आसान है। उपरोक्त कोड में, चर 'q' को दो बार आरंभीकृत किया गया है। और यदि आप धाराप्रवाह कोड को देखते हैं तो त्रुटि खराब दिखाई देती है। यदि आप अब उसी क्रम में वैरिएबल को इनिशियलाइज़ करते हैं जैसा कि वे घोषित किए जाते हैं, तो बस ऐसी कोई त्रुटि नहीं होगी। बेहतर कोड संस्करण:
 struct T { int x, y, z; float m; int q, w, e, r, t; } A; ... Ax = 0; Ay = 0; Az = 0; Am = 0.0; Aq = 0; Aw = 0; Ae = 1; Ar = 1; At = 1; 

बेशक, मुझे पता है कि उनकी घोषणा के क्रम में चर के साथ लिखना और काम करना हमेशा संभव नहीं होता है। लेकिन अक्सर यह संभव और उपयोगी है। एक अतिरिक्त लाभ यह होगा कि आपके लिए कोड में नेविगेट करना आसान होगा।

सिफारिश। नया चर जोड़ते समय, इसे अन्य चर के सापेक्ष अपनी स्थिति के अनुसार आरंभ करने और संसाधित करने का प्रयास करें।

2. टेबुलर तरीके अच्छे हैं।


अध्याय N18 [3] में पुस्तक "परफेक्ट कोड" में एस। मैककोनेल द्वारा सारणीबद्ध तरीके अच्छी तरह से लिखे गए हैं:

एक तालिका विधि एक ऐसी योजना है जो आपको तार्किक अभिव्यक्तियों जैसे कि अगर और मामले का उपयोग करने के बजाय एक तालिका में जानकारी खोजने के लिए अनुमति देती है। लगभग सभी चीजें जो आप तार्किक ऑपरेटरों के माध्यम से चुन सकते हैं, उन्हें तालिकाओं का उपयोग करके चुना जा सकता है। साधारण मामलों में, तार्किक अभिव्यक्तियाँ सरल और स्पष्ट होती हैं। लेकिन तार्किक निर्माणों की जटिलता के साथ, टेबल अधिक आकर्षक हो जाते हैं।

इसलिए, यह एक दया है कि प्रोग्रामर अभी भी विशाल स्विच () या अगर-और के घने जंगल पसंद करते हैं। खुद पर काबू पाना बहुत मुश्किल है। ऐसा लगता है, ठीक है, एक और "मामला:" या थोड़ा "अगर" चोट नहीं पहुंचेगी। लेकिन वह परेशान करता है। और सबसे अनुभवी प्रोग्रामर के लिए भी नई शर्तों को असफल रूप से जोड़ें। एक उदाहरण के रूप में, क्यूटी में दोषों की एक जोड़ी मिली।
 int QCleanlooksStyle::pixelMetric(...) { int ret = -1; switch (metric) { ... case PM_SpinBoxFrameWidth: ret = 3; break; case PM_MenuBarItemSpacing: ret = 6; case PM_MenuBarHMargin: ret = 0; break; ... } 

लंबे समय तक स्विच ()। और, ज़ाहिर है, एक विस्मृत विराम कथन है। विश्लेषक ने इस त्रुटि का पता लगाया इस तथ्य के कारण कि "रिट" चर को एक पंक्ति में दो बार एक अलग मूल्य सौंपा गया है।

शायद, यह बहुत बेहतर होगा यदि हमारे पास किसी प्रकार का std :: map <PixelMetric, int> और स्पष्ट रूप से एक संकेत के साथ मीट्रिक और संख्याओं के बीच लेबल सेट करें। आप फ़ंक्शन को लागू करने के लिए अन्य सारणीबद्ध विकल्पों के साथ आ सकते हैं।

एक और उदाहरण:
 QStringList ProFileEvaluator::Private::values(...) { ... else if (ver == QSysInfo::WV_NT) ret = QLatin1String("WinNT"); else if (ver == QSysInfo::WV_2000) ret = QLatin1String("Win2000"); else if (ver == QSysInfo::WV_2000) <<<=== 2003 ret = QLatin1String("Win2003"); else if (ver == QSysInfo::WV_XP) ret = QLatin1String("WinXP"); ... } 

कोड में, हम वेरिएबल 'वेर' की तुलना लगातार WV_2000 के साथ दो बार करते हैं। एक अच्छा उदाहरण वह स्थान है जहां सारणीबद्ध विधि के लिए जगह है। उदाहरण के लिए, ऐसी विधि इस तरह दिख सकती है:
 struct { QSysInfo::WinVersion; m_ver; const char *m_str; } Table_WinVersionToString[] = { { WV_Me, "WinMe" }, { WV_95, "Win95" }, { WV_98, "Win98" }, { WV_NT, "WinNT" }, { WV_2000, "Win2000" }, { WV_2003, "Win2003" }, { WV_XP, "WinXP" }, { WV_VISTA,"WinVista" } }; ret = QLatin1String("Unknown"); for (size_t i = 0; i != count_of(Table_WinVersionToString); ++i) if (Table_WinVersionToString[i].m_ver == ver) ret = QLatin1String(Table_WinVersionToString[i].m_str); 

बेशक, यह सिर्फ एक प्रोटोटाइप है, लेकिन यह सारणीबद्ध तरीकों के विचार को अच्छी तरह से प्रदर्शित करता है। सहमत हूं कि ऐसी तालिका में त्रुटि की पहचान करना बहुत आसान है।

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

3. विविध दिलचस्प


चूंकि क्यूटी एक बड़ा पुस्तकालय है, इसके उच्च गुणवत्ता के बावजूद, इसमें कई प्रकार की त्रुटियां पाई जा सकती हैं। बड़ी संख्या का कानून लागू होता है। * .Cpp, * .h और Qt प्रोजेक्ट की समान फ़ाइलों का आकार लगभग 250 मेगाबाइट है। चूंकि एक त्रुटि की संभावना नहीं है, एक बड़े कोड में इसे पूरा करना काफी संभव है। अन्य त्रुटियों के आधार पर, जिन्हें मैंने Qt में खोजा था, कुछ सिफारिशें करना मुश्किल है। मैं सिर्फ कुछ गलतियों का वर्णन करता हूं जो मुझे पसंद आईं।
 QString decodeMSG(const MSG& msg) { ... int repCount = (lKeyData & 0xffff); // Bit 0-15 int scanCode = (lKeyData & 0xf0000) >> 16; // Bit 16-23 bool contextCode = (lKeyData && 0x20000000); // Bit 29 bool prevState = (lKeyData && 0x40000000); // Bit 30 bool transState = (lKeyData && 0x80000000); // Bit 31 ... } 

&& ऑपरेटर का उपयोग & के बजाय यादृच्छिक रूप से किया जाता है। नोट करें कि कोड में टिप्पणी करना कितना उपयोगी है। यह तुरंत स्पष्ट हो जाता है कि यह वास्तव में एक गलती है और बिट्स को वास्तव में कैसे संसाधित किया जाना चाहिए।

निम्नलिखित उदाहरण लंबे भावों के बारे में होगा:
 static ShiftResult shift(...) { ... qreal l = (orig->x1 - orig->x2)*(orig->x1 - orig->x2) + (orig->y1 - orig->y2)*(orig->y1 - orig->y1) * (orig->x3 - orig->x4)*(orig->x3 - orig->x4) + (orig->y3 - orig->y4)*(orig->y3 - orig->y4); ... } 

गलती देखिए? यह सही है, और आप इस कदम पर ध्यान नहीं देंगे। ठीक है, मैं आपको बताता हूँ। यहां परेशानी यह है: "मूल-> y1 - मूल-> y1"। फिर भी, तीसरा गुणन मुझे परेशान करता है, लेकिन शायद यह सही है।

हां, एक और सवाल। और आप, आखिरकार, कार्यक्रमों में ऐसे गणना ब्लॉक भी हैं? क्या यह पीवीएस-स्टूडियो स्टैटिक कोड एनालाइज़र आज़माने का समय है? तो, विज्ञापित। और आगे चलते हैं।

असिंचित चर का उपयोग। वे किसी भी बड़े अनुप्रयोग में पाए जा सकते हैं:
 PassRefPtr<Structure> Structure::getterSetterTransition(Structure* structure) { ... RefPtr<Structure> transition = create( structure->storedPrototype(), structure->typeInfo()); transition->m_propertyStorageCapacity = structure->m_propertyStorageCapacity; transition->m_hasGetterSetterProperties = transition->m_hasGetterSetterProperties; transition->m_hasNonEnumerableProperties = structure->m_hasNonEnumerableProperties; transition->m_specificFunctionThrashCount = structure->m_specificFunctionThrashCount; ... } 

यहां फिर से, आपको एक संकेत देने की आवश्यकता है ताकि आपकी आंखों को लंबे समय तक पीड़ा न हो। आपको "संक्रमण-> m_hasGetterSetterProperties" चर के आरंभीकरण को देखने की जरूरत है।

मुझे यकीन है कि हम में से लगभग सभी ने, जब हमने पहली बार प्रोग्रामिंग शुरू की थी, आत्मा में गलती की थी:
 const char *p = ...; if (p == "12345") 

और उसके बाद ही यह एहसास हुआ कि पहली नज़र के कार्यों में अजीबों की आवश्यकता क्यों थी, जैसे स्ट्रैम्प ()। दुर्भाग्य से, सी ++ भाषा इतनी कठोर है कि आप कई वर्षों के बाद ऐसी गलती कर सकते हैं, अनुभव के साथ एक पेशेवर डेवलपर होने के नाते:
 const TCHAR* getQueryName() const; ... Query* MultiFieldQueryParser::parse(...) { ... if (q && (q->getQueryName() != _T("BooleanQuery") ... ... } 

और क्या दिखाना है। उदाहरण के लिए, चर मानों की गलत लिखित विनिमय।
 bool qt_testCollision(...) { ... t=x1; x1=x2; x2=t; t=y1; x1=y2; y2=t; ... } 

यह बहुत सरल कोड में भी गलतियाँ करने का एक उदाहरण है। इसलिए, किसी सरणी की सीमाओं से परे जाने के विषय पर अभी तक कोई उदाहरण नहीं आया है। अब यह होगा:
 bool equals( class1* val1, class2* val2 ) const { ... size_t size = val1->size(); ... while ( --size >= 0 ){ if ( !comp(*itr1,*itr2) ) return false; itr1++; itr2++; } ... } 

स्थिति "- आकार> = 0" हमेशा सही होती है, क्योंकि आकार चर का एक अहस्ताक्षरित प्रकार होता है। यदि समान अनुक्रमों की तुलना की जाती है, तो सरणियों की सीमा पार हो जाएगी।

आप पर और पर जा सकते हैं। मुझे आशा है कि जैसा कि प्रोग्रामर समझते हैं कि एक लेख में इस तरह के वॉल्यूम की परियोजना में त्रुटियों का वर्णन करने का कोई तरीका नहीं है। इसलिए, आखिरी बार, नाश्ते के लिए:
 STDMETHODIMP QEnumPins::QueryInterface(const IID &iid,void **out) { ... if (S_OK) AddRef(); return hr; } 

"If (hr == S_OK)" या "if (SUCCEEDED (hr))" की भावना में कुछ होना चाहिए था। मैक्रो S_OK 0. के अलावा और कुछ नहीं है। इसलिए, लिंक की संख्या की गलत गणना के साथ एक बायक यहां अपरिहार्य है।

एक निष्कर्ष के बजाय


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

मैं पाठकों का भी आभारी रहूंगा यदि आप मुझे उन त्रुटियों के दिलचस्प उदाहरण भेजते हैं जो आपको अपने या किसी और के कोड में मिली हैं और जिसके लिए आप नैदानिक ​​नियमों को लागू कर सकते हैं।

ग्रंथ सूची


  1. एंड्रे कार्पोव। कोड लिखने के चरण में त्रुटियों की संभावना को कैसे कम करें। नोट N १। http://habrahabr.ru/blogs/cpp/115143/
  2. एंड्रे कार्पोव। कोड लिखने के चरण में त्रुटियों की संभावना को कैसे कम करें। नोट N2। http://habrahabr.ru/blogs/cpp/116397/
  3. मैककोनेल एस परफेक्ट कोड। मास्टर वर्ग / प्रति। अंग्रेजी से - एम।: प्रकाशन और ट्रेडिंग हाउस "रूसी संस्करण"; सेंट पीटर्सबर्ग: पीटर, 2005 ।-- 896 पीपी ।: बीमार।

Source: https://habr.com/ru/post/In123603/


All Articles