सारांश
जो प्रोग्राम ग़लत परिणाम देता है, उसे पंक्तियाँ मनमाने ढंग से बदलकर नहीं, बल्कि जाँच-पड़ताल करके सुधारा जाता है। यह पाठ डिबगिंग को एक प्रायोगिक विधि के रूप में प्रस्तुत करता है: विफलता को पुनरुत्पादित करना, उसके कारण के बारे में एक परिकल्पना बनाना, ऐसा प्रयोग रचना जो उसकी पुष्टि करे या उसे ख़ारिज करे, और दोष के स्थान का दायरा सीमित करना; द्विभाजन उसे उतने चरणों में ढूँढ़ लेता है जितने प्रोग्राम के आकार के लघुगणक की तरह बढ़ते हैं। यह उपकरण को विस्तार से समझाता है (-g के साथ और ऑप्टिमाइज़ेशन के बिना कंपाइल करना, तीनों सिस्टमों में Visual Studio Code का कॉन्फ़िगरेशन, और GDB का एक वास्तविक सत्र जो दिखाता है कि संपादक पर्दे के पीछे क्या करता है), साधारण और सशर्त ब्रेकपॉइंट, चरण-दर-चरण निष्पादन, कॉल स्टैक और अवस्था का निरीक्षण, एक केंद्रीय उदाहरण के सहारे: वह माध्यिका जो केवल सम संख्या में आँकड़ों पर विफल होती है। अंत में वे रणनीतियाँ आती हैं जो उपकरण से अधिक मूल्यवान हैं (मामले को छोटा करना, अंतिम सही बिंदु खोजना, assert से निश्चरों की जाँच करना, डिटेक्टर की रिपोर्ट पढ़ना), और तीन मामले: एक के अंतर की त्रुटि, एक पुनरावर्तन जो समाप्त नहीं होता, और एक अमान्य रेफ़रेंस।
अधिगम उद्देश्य
- डिबगिंग जानकारी के साथ कंपाइलेशन (
-g, ऑप्टिमाइज़ेशन के बिना) कॉन्फ़िगर करना और अपने सिस्टम पर Visual Studio Code से डिबगर शुरू करना। - साधारण और सशर्त ब्रेकपॉइंट, चरण-दर-चरण निष्पादन (भीतर जाना, लाँघना, बाहर निकलना) और चरों, व्यंजकों तथा कॉल स्टैक के निरीक्षण का उपयोग करना।
- किसी दिए गए दोष का स्थान खोजने के लिए एक व्यवस्थित रणनीति (पुनरुत्पादन, छोटा करना, द्विभाजन, परिकल्पनाएँ बनाना और उन्हें परखना) लागू करना, और हर परिकल्पना को दर्ज करना।
- डिबगर को डिटेक्टरों और परीक्षणों के साथ मिलाकर, किसी मेमोरी त्रुटि का स्थान उसकी रिपोर्ट से खोजना।
डिबग करना जाँच-पड़ताल करना है
दोष और विफलता
विफलता वह है जो देखी जाती है: ग़लत परिणाम, ऐसा प्रोग्राम जो समाप्त नहीं होता, डिटेक्टर की रिपोर्ट। दोष (या त्रुटि) कारण है: कोई पंक्ति, कोई शर्त या कोई डिज़ाइन-निर्णय जो विनिर्देश के अनुरूप नहीं है। विफलता दिखती है; दोष को खोजना पड़ता है, और वह उससे दूर भी हो सकता है। इससे यह निकलता है कि जहाँ विफलता दिखती है वहीं सुधार करना, उदाहरण के लिए परिणाम में से 2 घटा देना क्योंकि “हमेशा 2 ज़्यादा आता है”, लक्षण को सुधारता है और कारण को छोड़ देता है, जो दूसरे आँकड़ों के साथ लौट आएगा।
यदि ऐसा है, तो डिबग करना कारण की जाँच-पड़ताल है, और उसकी विधि प्रायोगिक विज्ञानों की विधि है:
- अवलोकन और पुनरुत्पादन। विफलता का सटीक वर्णन करना (किस इनपुट के साथ, क्या अपेक्षित था, क्या मिला) और ऐसा करना कि वह जब चाहें तब घटित हो। जो दोष पुनरुत्पादित नहीं होता, उसकी जाँच नहीं की जा सकती, और न ही यह जाँचा जा सकता है कि वह सुधर गया।
- परिकल्पना बनाना। कारण के बारे में ऐसा कथन जो असत्य सिद्ध हो सकता हो: “क्रमबद्धन सूची को क्रमबद्ध नहीं करता”, “बीच के दूसरे आँकड़े का सूचकांक एक अधिक है”।
- प्रयोग रचना। प्रोग्राम का एक बिंदु चुनना और यह पूर्वानुमान करना कि यदि परिकल्पना सत्य है तो वहाँ कौन-सी अवस्था दिखनी चाहिए, और यदि असत्य है तो कौन-सी।
- निष्कर्ष और दायरा सीमित करना। परिकल्पना को ख़ारिज करना या उसकी पुष्टि करना; दोनों ही दशाओं में संदिग्ध हिस्सा छोटा हो जाता है। यह तब तक दोहराया जाता है जब तक हिस्सा एक पंक्ति न रह जाए।
किंतु यह विधि अनुशासन माँगती है: एक बार में केवल एक चीज़ बदलना, हर परिकल्पना को उसके परिणाम के साथ लिखना, और जब तक पता न हो कि क्या खोजा जा रहा है तब तक कोड को न छूना। आउटपुट के सही दिखने तक पंक्तियाँ मनमाने ढंग से बदलते रहना दोष को हटाए बिना विफलता को छिपा सकता है।
आँकड़ों की किसी सूची की माध्यिका क्रमबद्ध सूची का बीच का आँकड़ा है यदि संख्या विषम हो, और बीच के दो आँकड़ों का औसत है यदि सम हो। पाठ प्रोग्रामों का परीक्षण ने दिखाया कि परीक्षण यह पकड़ लेते हैं कि कोई फ़ंक्शन कब विफल होता है, पर यह नहीं कि उसका दोष कहाँ है। नीचे दिया गया फ़ंक्शन पाँच आँकड़ों के साथ परीक्षण पास करता है और चार के साथ नहीं।
#include <algorithm>#include <cstddef>#include <print>#include <vector>// आँकड़ों की एक सूची की माध्यिका।// पूर्व-शर्त: values खाली नहीं है।// पश्च-शर्त: क्रमबद्ध सूची का बीच का आँकड़ा, या बीच के दो आँकड़ों का औसत लौटाता है।double median(std::vector<double> values){ std::ranges::sort(values); const std::size_t n{values.size()}; const std::size_t middle{n / 2}; if (n % 2 == 1) { return values[middle]; } return (values[middle] + values[middle + 1]) / 2.0;}int main(){ const std::vector<double> odd{7.0, 1.0, 5.0, 3.0, 9.0}; const std::vector<double> even{7.0, 1.0, 5.0, 3.0}; std::println("{} की माध्यिका: {}", odd, median(odd)); std::println("{} की माध्यिका: {}", even, median(even));}[7, 1, 5, 3, 9] की माध्यिका: 5[7, 1, 5, 3] की माध्यिका: 6अवलोकन सटीक है: के साथ, जो क्रमबद्ध होकर बनता है, अपेक्षा की थी और मिला 6; पाँच आँकड़ों के साथ परिणाम सही है। कम से कम तीन परिकल्पनाएँ संभव हैं: (H1) क्रमबद्धन क्रमबद्ध नहीं करता; (H2) बीच के दो आँकड़ों के सूचकांक ग़लत हैं; (H3) योग या भाग ग़लत लिखा गया है। हर परिकल्पना पंक्ति 17 के बारे में, उसके निष्पादन से पहले, कुछ अलग पूर्वानुमान करती है: यदि H1 सत्य है, तो values क्रमबद्ध नहीं है; यदि H2 सत्य है, तो values[middle] और values[middle + 1] 3 और 5 नहीं हैं; यदि H3 सत्य है, तो दोनों आँकड़े 3 और 5 हैं और फिर भी गणना 6 देती है। विषम वाले मामले का ठीक चलना H1 को असंभाव्य बनाता है, पर “असंभाव्य” का अर्थ “ख़ारिज” नहीं है; डिबगर के साथ प्रयोग यह है कि प्रोग्राम को पंक्ति 17 पर रोककर देखा जाए।
द्विभाजन
जब कोई स्पष्ट परिकल्पना न हो, तब भी एक ऐसी विधि बचती है जो विफल नहीं होती: द्विभाजन। यह माना जाता है कि प्रोग्राम की अवस्था आरंभ में सही है और अंत में ग़लत, और हर बीच के बिंदु पर यह तय किया जा सकता है कि वह सही है या नहीं (हाथ से निकाले गए मान से तुलना करके, या किसी निश्चर की जाँच करके)। संदिग्ध हिस्से के बीच में अवस्था की जाँच की जाती है: यदि वह सही है, तो दोष दूसरे आधे भाग में है; यदि नहीं, तो पहले में। यह पाठ खोजना और क्रमबद्ध करना की द्विआधारी खोज है, जो किसी प्रोग्राम की पंक्तियों पर (या किसी लूप के चक्करों पर, या किसी फ़ाइल के संस्करणों पर) लागू की गई है।
चित्र सोलह पंक्तियों के द्विभाजन का अनुसरण करता है, जिनमें दोष पंक्ति 11 में है; हर निशान उस पंक्ति के बाद की अवस्था पर एक प्रयोग है: सही (✓) या ग़लत (✗)।
प्रमाण
आरंभ में सोलहों पंक्तियाँ संदिग्ध हैं: यह ज्ञात है कि अवस्था पंक्ति 1 से पहले सही है और पंक्ति 16 के बाद ग़लत।
पंक्ति 8 के बाद अवस्था सही है: पंक्तियाँ 1 से 8 तक दोषमुक्त हो जाती हैं।
पंक्ति 12 के बाद अवस्था पहले ही ग़लत है: दोष पंक्ति 9 और 12 के बीच है।
पंक्ति 10 के बाद सही: पंक्तियाँ 11 और 12 बचती हैं।
पंक्ति 11 के बाद ग़लत: अंतिम सही बिंदु और पहला ग़लत बिंदु पड़ोसी हैं, और दोष पंक्ति 11 में है।
गिनती वही है जो द्विआधारी खोज की है: हर प्रयोग हिस्से को दो भागों में बाँट देता है, इसलिए प्रयोग पंक्तियों के लिए पर्याप्त हैं। एक हज़ार पंक्तियों के प्रोग्राम के लिए दस पर्याप्त हैं, क्योंकि । इसी से विधि का लाभ निकलता है: इसकी लागत आकार के लघुगणक की तरह बढ़ती है, और यह पूरे प्रोग्राम को समझने की माँग नहीं करती, बल्कि यह तय कर पाने की कि किसी बिंदु पर अवस्था वैसी है या नहीं जैसी होनी चाहिए। किंतु यही निर्णय कठिन हिस्सा है: विनिर्देश और निश्चर ही वह कसौटी हैं जिससे हर बीच की अवस्था को परखा जाता है।
सामग्री लोड हो रही है…