जानें कि टोनी हॉअर के Hoare लॉजिक, Quicksort और सुरक्षा सोच ने सही सॉफ़्टवेयर लिखने और समीक्षा करने की व्यावहारिक तकनीकों को कैसे आकार दिया।

जब लोग कहते हैं कि कोई प्रोग्राम “सही” है, तो अक्सर उनका मतलब होता है: “मैंने इसे कुछ बार चलाया और आउटपुट ठीक दिखा।” यह एक उपयोगी संकेत है—पर यह सहीपन नहीं है। सादे शब्दों में, सहीपन का मतलब है कि प्रोग्राम अपनी स्पेसिफ़िकेशन को पूरा करता है: हर स्वीकार्य इनपुट के लिए वह आवश्यक परिणाम देता है और राज्य परिवर्तनों, समयबद्धता, और त्रुटियों के बारे में किसी भी नियम का सम्मान करता है।
समस्या यह है कि “अपनी स्पेसिफ़िकेशन को पूरा करना” जितना आसान सुनता है, उतना आसान नहीं है।
पहला, स्पेसिफ़िकेशन अक्सर अस्पष्ट होते हैं। एक प्रोडक्ट रिक्वायरमेंट कह सकता है “लिस्ट सॉर्ट करो”, पर क्या उस से मतलब stable sorting है? डुप्लिकेट मानों, खाली सूचियों, या नॉन-न्यूमेरिक आइटम्स का क्या? अगर स्पेक में नहीं लिखा, तो अलग लोग अलग मत मान लेंगे।
दूसरा, एज केस दुर्लभ नहीं हैं—वे बस कम बार टेस्ट किए जाते हैं। नल मान, ओवरफ़्लो, ऑफ-बाय-वन सीमाएँ, असामान्य उपयोगकर्ता अनुक्रम, और अप्रत्याशित बाहरी विफलताएँ “ऐसा लगता है काम कर रहा है” को “प्रोडक्शन में फेल हो गया” बना सकती हैं।
तीसरा, आवश्यकताएँ बदलती हैं। एक प्रोग्राम कल की स्पेक के अनुसार सही हो सकता है और आज की स्पेक के अनुसार गलत।
टोनी हॉअर का बड़ा योगदान यह नहीं था कि हमें हर चीज़ का हर समय प्रमाण करना चाहिए। उसका विचार यह था कि हम यह अधिक सटीक तरीके से समझ सकते हैं कि कोड से क्या अपेक्षित है—और अनुशासित तरीके से उस पर तर्क कर सकते हैं।
इस पोस्ट में हम तीन जुड़े धागों का पालन करेंगे:
ज़्यादातर टीमें पूरे औपचारिक प्रमाण नहीं लिखेंगी। पर आंशिक, “प्रूफ़-स्टाइल” सोच भी बग पकड़ना आसान बना सकती है, रिव्यूज़ को तेज कर सकती है, और कोड शिप करने से पहले व्यवहार को स्पष्ट कर सकती है।
टोनी हॉअर उन दुर्लभ कंप्यूटर वैज्ञानिकों में हैं जिनका काम सिर्फ पेपर या कक्षा में नहीं रहा। वह अकादमी और उद्योग के बीच रहे, और उन्होंने एक व्यावहारिक प्रश्न पर ध्यान दिया जो हर टीम आज भी सामना करती है: जब दांव ऊँचा हो, तब हम कैसे जानें कि प्रोग्राम वही करता है जो हम सोचते हैं?
यह लेख कुछ हॉअर विचारों पर केंद्रित है जो असल कोडबेस में बार-बार दिखाई देते हैं:
{P} C {Q} का इस्तेमाल कर प्रोग्राम व्यवहार का वर्णन करने का तरीका।यहाँ आप गहन गणितीय औपचारिकता नहीं पाएँगे, और हम Quicksort का पूरा, मशीन-चेकेबल प्रमाण करने का प्रयास भी नहीं करेंगे। लक्ष्य है कि अवधारणाएँ सुलभ बनी रहें: इतना ढाँचा की आपकी तर्कशक्ति साफ़ हो जाए, बिना समीक्षा को स्नातक स्तर के सेमिनार में बदल दिए।
हॉअर के विचार रोज़मर्रा के निर्णयों में बदल जाते हैं: किसी फ़ंक्शन पर कौन-सी धारणाएँ निर्भर हैं, कॉलरों को क्या गारंटी दी जाती है, लूप के बीच में क्या सच रहना चाहिए, और रिव्यू के दौरान “लगभग सही” परिवर्तन को कैसे देखा जाए। भले ही आप कभी {P} C {Q} लिखें नहीं, उस ढाँचे में सोचना APIs, टेस्ट्स और जटिल कोड पर चर्चाओं की गुणवत्ता सुधारता है।
हॉअर का नजरिया “कुछ उदाहरण पास हो गए” से कड़ा है: सहीपन एक सहमत वादा पूरा करने के बारे में है, न कि कुछ छोटे नमूनों में सही दिखने के बारे में।
बग अक्सर तब होते हैं जब टीमें बीच का चरण छोड़ देती हैं: वे सीधे requirements से कोड पर कूद जाती हैं, जिससे “वादा” धुँधला रह जाता है।
दो अलग दावे अक्सर एक साथ गड़बड़ कर दिए जाते हैं:
वास्तविक सिस्टम में “कभी खत्म न होना” उतना ही हानिकारक हो सकता है जितना “गलत उत्तर देना”।
सहीपन के बयान सार्वभौमिक नहीं होते; वे इन बातों पर निर्भर करते हैं:
धारणाओं को स्पष्ट करना “मेरी मशीन पर काम करता है” को दूसरों के लिए तर्कयोग्य बनाता है।
एक फ़ंक्शन sortedCopy(xs) पर विचार करें।
एक उपयोगी स्पेक हो सकता है: “एक नई लिस्ट ys लौटाता है ऐसी कि (1) ys आरोही क्रम में है, और (2) ys में ठीक वैसे ही तत्व हैं जैसे xs में (गिनती सहित), और (3) xs अपरिवर्तित है।”
अब “सही” का मतलब है कि कोड इन तीन बिंदुओं को बताई गई धारणाओं के अधीन पूरा करता है—सिर्फ़ यह नहीं कि आउटपुट जल्दी टेस्ट में सॉर्टेड लगता है।
Hoare लॉजिक कोड के बारे में उसी स्पष्टता से बात करने का तरीका है जैसा आप किसी कॉन्ट्रैक्ट के बारे में कहते: यदि आप एक ऐसी स्थिति से शुरू करते हैं जो कुछ शर्तें पूरा करती है, और आप यह कोड चलाते हैं, तो आप ऐसी स्थिति में खत्म होंगे जो कुछ गारंटियाँ पूरा करती है।
मुख्य संकेतक है Hoare ट्रिपल:
{precondition} program {postcondition}
एक प्रीकॉन्डिशन बताता है कि प्रोग्राम फ्रैग्मेंट चलने से पहले क्या सच होना चाहिए। यह आशा नहीं है—यह वह चीज़ है जिसकी कोड को आवश्यकता है।
उदाहरण: मानिए एक फ़ंक्शन दो संख्याओं का औसत लौटाता है बिना ओवरफ़्लो चेक के।
a + b इन्टीजर टाइप में फिट होता हैavg = (a + b) / 2avg वास्तविक औसत के बराबर हैयदि प्रीकॉन्डिशन नहीं रहता (ओवरफ़्लो संभव है), तो पोस्टकंडीशन का वादा लागू नहीं होता। ट्रिपल आपको इसे ज़ोर देकर कहने पर मजबूर करता है।
एक पोस्टकंडीशन बताता है कि कोड चलने के बाद क्या सच होगा—जैसा कि प्रीकॉन्डिशन मान्य था। अच्छे पोस्टकंडीशन ठोस और परखने योग्य होते हैं। “रिज़ल्ट वैध है” कहने के बजाय बताइए कि “वैध” का मतलब क्या है: सॉर्टेड, नॉन-नेगेटिव, सीमा के भीतर, सिर्फ़ कुछ फ़ील्ड्स बदले हुए, आदि।
Hoare लॉजिक छोटे स्टेटमेंट्स से लेकर बहु-स्टेप कोड तक बढ़ता है:
x = x + 1 के बाद x के बारे में कौन से तथ्य सच हैं?बिंदु यह नहीं है कि हर जगह कर्ली ब्रेसेज़ छिड़क दें। बिंदु यह है कि इरादा पठनीय बने: स्पष्ट धारणाएँ, स्पष्ट परिणाम, और रिव्यूज़ में कम “ऐसा लगता है काम कर रहा है” वार्तालाप।
एक लूप इनवेरिएंट वह कथन है जो लूप शुरू होने से पहले सच होता है, हर इटरेशन के बाद सच रहता है, और जब लूप खत्म होता है तब भी सच रहता है। यह साधारण विचार बड़ा लाभ देता है: यह “ऐसा लगता है काम कर रहा है” को उस दावे के साथ बदल देता है जिसे आप वास्तव में हर कदम पर जाँच सकते हैं।
बिना इनवेरिएंट के, रिव्यू अक्सर कुछ ऐसा लगता है: “हम सूची पर इटरेट करते हैं और धीरे-धीरे चीज़ें ठीक करते हैं।” एक इनवेरिएंट सटीकता पर मजबूर करता है: अभी किस बात पर भरोसा किया जा सकता है जबकि लूप पूरा नहीं हुआ? एक बार आप यह स्पष्ट कह सकें, तो ऑफ-बाय-वन एरर और छूटी हुई केसें आसानी से दिखने लगती हैं, क्योंकि वे ऐसे क्षणों पर प्रकट होंगी जहाँ इनवेरिएंट टूट जाएगा।
अधिकतर रोज़मर्रा के कोड कुछ भरोसेमंद टेम्पलेट्स का उपयोग कर सकते हैं।
1) बाउंड्स / इंडेक्स सुरक्षा
इंडेक्सेज़ को सुरक्षित रेंज में रखें।
0 <= i <= nlow <= left <= right <= highयह प्रकार का इनवेरिएंट आउट-ऑफ-रेंज एक्सेस को रोकने और एरे तर्क को ठोस बनाने के लिए शानदार है।
2) प्रोसेस्ड बनाम अनप्रोसेस्ड आइटम्स
अपने डेटा को “हो चुका” और “अभी तक नहीं” के हिस्सों में बाँटें।
a[0..i) में जांचे जा चुके हैं।”result में भेजा गया है, फिल्टर प्रेडिकेट को संतुष्ट करता है।”यह अस्पष्ट प्रगति को उस परिभाषा में बदल देता है कि “प्रोसेस्ड” क्या है।
3) सॉर्टेड प्रीफ़िक्स (या पार्टिशन्ड प्रीफ़िक्स)
सॉर्टिंग, मर्जिंग, और पार्टिशनिंग में सामान्य।
a[0..i) सॉर्टेड है।”a[0..i) के सभी आइटम ≤ pivot हैं, और a[j..n) के सभी आइटम ≥ pivot हैं।”भले ही पूरा एरे अभी सॉर्टेड न हो, आप यह पिन कर चुके हैं कि क्या है।
सहीपन सिर्फ सही होने के बारे में नहीं है; लूप को खत्म भी होना चाहिए। इसे तर्क देने का एक सरल तरीका है कि एक माप नामित करें (अक्सर variant) जो हर इटरेशन में घटता है और हमेशा घट नहीं सकता।
उदाहरण:
n - i हर बार 1 से घटता है।”यदि आप घटने वाला माप नहीं पा सकते, तो यह एक वास्तविक जोखिम का संकेत है: कुछ इनपुट पर अनंत लूप।
Quicksort का सरल वादा है: दी गई स्लाइस (या एरे सेगमेंट) के तत्वों को पुनर्व्यवस्थित करें ताकि वे गैर-घटते क्रम में आ जाएँ, बिना किसी मान को खोए या नए मान बना दिए। एल्गोरिथ्म का उच्च-स्तरीय रूप आसान है:
यह एक बेहतरीन शिक्षण उदाहरण है क्योंकि यह इतना छोटा है कि दिमाग में रखा जा सकता है, पर इतना समृद्ध भी है कि अनौपचारिक तर्क कहाँ फेल हो जाते हैं यह दिखता है। कुछ यादृच्छिक टेस्ट पर सही लगता Quicksort फिर भी कुछ विशेष इनपुट या सीमांत स्थितियों पर गलत हो सकता है।
कुछ मुद्दे ज्यादातर बग्स का कारण बनते हैं:
Hoare-शैली तरीके से तर्क करने के लिए आप आम तौर पर प्रमाण को दो हिस्सों में बाँटते हैं:
यह विभाजन तर्क को प्रबंधनीय बनाता है: पार्टिशन सही करो, फिर उस पर सॉर्टिंग सहीपन बनाओ।
Quicksort की गति एक चालाक रूटीन पर निर्भर करती है: पार्टिशन। अगर पार्टिशन थोड़ी भी गलत है, Quicksort कुछ भी कर सकता है: गलत सॉर्ट, अनंत रेकर्सन, या एज केस पर क्रैश।
हम क्लासिक Hoare partition scheme (दो पॉइंटर्स अंदर की ओर बढ़ते हैं) लेंगे।
इनपुट: एरे स्लाइस A[lo..hi] और एक चुना हुआ pivot मान (अक्सर A[lo]).
आउटपुट: एक इंडेक्स p ऐसा कि:
A[lo..p] का हर तत्व <= pivot हैA[p+1..hi] का हर तत्व >= pivot हैध्यान दें कि क्या वादा नहीं किया गया: pivot जरूरी नहीं कि p पर आए, और pivot के बराबर तत्व दोनों तरफ़ हो सकते हैं। यह ठीक है—Quicksort को केवल सही स्प्लिट चाहिए।
जब एल्गोरिद्म दो इंडेक्स बढ़ाता है—बाएं से i और दाएं से j—तो अच्छा तर्क इस पर केन्द्रित होता है कि क्या पहले से “लॉक इन” है। एक व्यावहारिक इनवेरिएंट सेट है:
A[lo..i-1] के सभी आइटम <= pivot हैं (बाया हिस्सा साफ़ है)A[j+1..hi] के सभी आइटम >= pivot हैं (दायां हिस्सा साफ़ है)A[i..j] में सब कुछ अवर्गीकृत है (अभी जांच होना बाकी)जब हमें A[i] >= pivot और A[j] <= pivot मिलते हैं और उन्हें स्वैप किया जाता है, तो ये इनवेरिएंट्स बने रहते हैं और अवर्गीकृत मध्य सिकुड़ता है।
i दाएँ तक चलेगा; पार्टिशन को फिर भी समाप्त होकर एक समझदार p लौटाना चाहिए।j बाएँ तक चलेगा; वही टर्मिनेशन चिंता है।< बनाम <=), पॉइंटर्स अटक सकते हैं। Hoare की स्कीम प्रोग्रेस के लिए सुसंगत नियमों पर निर्भर करती है।भिन्न पार्टिशन स्कीमें (Lomuto, Hoare, three-way) मौजूद हैं। मुख्य बात यह है कि एक चुनें, उसका कॉन्ट्रैक्ट स्पष्ट करें, और कोड की समिक्षा उसी कॉन्ट्रैक्ट के खिलाफ लगातार करें।
रेकर्सन पर भरोसा तब आसान होता है जब आप दो प्रश्न स्पष्ट उत्तर दे सकें: यह कब रुकता है? और हर कदम वैध क्यों है? Hoare-शैली सोच मदद करती है क्योंकि यह आपको बताने पर मजबूर करती है कि किसी कॉल से पहले क्या सच होना चाहिए और कॉल लौटने के बाद क्या सच होगा।
एक रेकर्सिव फ़ंक्शन को कम से कम एक बेस केस चाहिए जहाँ वह और कोई रेकर्सिव कॉल नहीं करता और फिर भी वादा पूरा करता है।
सॉर्टिंग के लिए सामान्य बेस केस है “लंबाई 0 या 1 वाली ऐरे पहले से सॉर्टेड है।” यहाँ “सॉर्टेड” स्पष्ट होना चाहिए: किसी ऑर्डरिंग रिलेशन ≤ के लिए आउटपुट सॉर्टेड है यदि हर इंडेक्स i < j के लिए a[i] ≤ a[j]। (बराबर तत्वों का मूल क्रम बने रहने को stability कहते हैं; Quicksort आम तौर पर stable नहीं होता जब तक आप इसे डिजाइन न करें)।
हर रेकर्सिव स्टेप को खुद को कठोर रूप से छोटा इनपुट देना चाहिए। यह “श्रिंकिंग” आपका टर्मिनेशन तर्क है: अगर साइज घटता है और नकारात्मक नहीं हो सकता, तो आप अनंत रेकर्सन नहीं कर सकते।
श्रिंकिंग स्टैक सुरक्षा के लिए भी मायने रखती है। सही कोड भी तब क्रैश कर सकता है जब रेकर्सन गहराई बहुत बड़ी हो। Quicksort में असंतुलित पार्टिशन गहरी रेकर्सन दे सकते हैं। यह टर्मिनेशन-प्रूफ़ के साथ एक व्यावहारिक अनुस्मारक भी है कि संभावित सबसे खराब गहराई पर विचार करें।
Quicksort का वर्स्ट-केस टाइम O(n²) तक गिर सकता है जब पार्टिशन बहुत असंतुलित हों, पर यह प्रदर्शन की समस्या है—सहीपन की विफलता नहीं। यहाँ तर्क का लक्ष्य यह है: मान लीजिए पार्टिशन ने तत्वों को बनाए रखा और pivot के अनुसार बाँटा, तो सबरेंज का रिकर्सिव सॉर्टिंग यह सुनिश्चित करता है कि पूरा एरे सॉर्टेड हो जाएगा।
टेस्टिंग और प्रूफ़-स्टाइल रीज़निंग का लक्ष्य एक ही है—विश्वास—पर रास्ते अलग हैं।
टेस्ट्स ठोस गलतियों को पकड़ने में शानदार हैं: एक ऑफ-बाय-वन, एक छूटा एज केस, या एक रिग्रेशन। पर एक टेस्ट सूट केवल इनपुट स्पेस का नमूना ले सकता है। यहाँ तक कि “100% कवरेज” भी “सभी व्यवहार जाँचे गए” नहीं दर्शाता; यह अक्सर सिर्फ़ “सभी लाइनें एक्सेक्यूट हुईं” मतलब होता है।
प्रूफ़-स्टाइल रीज़निंग (खासकर Hoare-शैली) स्पेसिफ़िकेशन से शुरू करती है और पूछती है: यदि ये प्रीकॉन्डिशन सच हैं, क्या कोड हमेशा पोस्टकंडीशन स्थापित करेगा? जब आप यह अच्छी तरह करते हैं, तो आप सिर्फ़ बग ढूँढते नहीं—आप अक्सर पूरे श्रेणी के बग्स (जैसे “एरे एक्सेस सीमा में रहता है” या “लूप पार्टिशन प्रॉपर्टी नहीं तोड़ता”) को समाप्त कर सकते हैं।
एक स्पष्ट स्पेक एक टेस्ट जनरेटर है।
यदि आपका पोस्टकंडीशन कहता है “आउटपुट सॉर्टेड है और इनपुट का परम्यूटेशन है”, तो आप स्वचालित रूप से टेस्ट आइडियाज़ पाते हैं:
स्पेक बताता है कि “सही” क्या है, और टेस्ट यह जाँचते हैं कि वास्तविकता उससे मेल खाती है।
प्रॉपर्टी-बेस्ड टेस्टिंग प्रूफ़ और उदाहरणों के बीच बैठती है। बजाय इसके कि आप कुछ मामलों को हाथ से चुनें, आप गुण बताते हैं और टूल कई इनपुट जेनरेट करता है।
सॉर्टिंग के लिए, दो सरल गुण बहुत काम आते हैं:
ये गुण व्यवहार में पोस्टकंडीशन के रूप में executable checks हैं।
एक हल्का रूटीन जो स्केल करता है:
यदि आप इसे संस्थागत बनाना चाहते हैं, तो “स्पेक + रीज़निंग नोट्स + टेस्ट” को अपने PR टेम्पलेट या कोड रिव्यू चेकलिस्ट का हिस्सा बनाइए (देखें भी /blog/code-review-checklist)।
यदि आप vibe-coding वर्कफ़्लो इस्तेमाल कर रहे हैं (चैट-आधारित इंटरफ़ेस से कोड जनरेट करना), तो वही अनुशासन लागू होता है—शायद और भी ज़्यादा। उदाहरण के लिए, Koder.ai में आप Planning Mode से प्रीकॉन्डिशन/पोस्टकंडीशन पिन कर सकते हैं पहले, फिर प्रॉपर्टी-बेस्ड टेस्ट जोड़ते हुए snapshots और rollback के साथ इटरेट कर सकते हैं। टूल इम्प्लीमेंटेशन तेज़ करता है, पर स्पेक वह चीज़ है जो “तेज़” को “टूटी-फूटी” से बचाती है।
सहीपन सिर्फ़ “प्रोग्राम सही वैल्यू लौटाए” नहीं है। सुरक्षा मानसिकता एक अलग प्रश्न पूछती है: कौन से परिणाम अस्वीकार्य हैं, और हम उन्हें कैसे रोकें—यहाँ तक कि जब कोड दबाव में हो, गलत उपयोग हो रहा हो, या आंशिक विफल हो? व्यवहार में, सुरक्षा ऐसे सहीपन के साथ प्राथमिकता प्रणाली है: कुछ विफलताएँ केवल झंझट हैं, कुछ वित्तीय नुकसान, प्राइवेसी उल्लंघन, या शारीरिक हानि कर सकती हैं।
एक बग कोड या डिजाइन में दोष है। एक हाज़र्ड ऐसी स्थिति है जो अस्वीकार्य परिणाम पैदा कर सकती है। एक ही बग एक संदर्भ में बेख़तरनाक हो सकता है और दूसरे में ख़तरनाक।
उदाहरण: फोटो गैलरी में ऑफ-बाय-वन गलती एक इमेज गलत लेबल कर सकती है; वही गलती दवा की डोज़ गणना में रोगी को हानि पहुंचा सकती है। सुरक्षा सोच आपको कोड व्यवहार को परिणामों से जोड़ने पर मजबूर करती है, सिर्फ़ “स्पेक अनुपालन” से नहीं।
###Worst-outcome रोकने के साधारण तरीके
भारी औपचारिक तरीकों की ज़रूरत नहीं—छोटी, दोहराने योग्य प्रथाएँ तुरंत सुरक्षा लाभ दे सकती हैं:
ये तकनीकें Hoare-शैली तर्क के साथ स्वाभाविक रूप से जोड़ी जाती हैं: आप प्रीकॉन्डिशन स्पष्ट करते हैं और पोस्टकंडीशन में सुरक्षा गुण शामिल करते हैं (क्या कभी नहीं होना चाहिए)।
सुरक्षा-जाँचें कुछ लागत मांगती हैं—CPU समय, जटिलता, या कभी-कभी गलत-इनकार।
सुरक्षा सोच सुंदरता सिद्ध करने की बजाय उन विफल मोड्स को रोकने पर केंद्रित है जिन्हें आप उठाने का जोखिम नहीं उठा सकते।
कोड रिव्यूज़ वही जगह है जहाँ सहीपन सोच सबसे तेज़ लाभ देती है, क्योंकि आप गायब धारणाओं को उत्पादन में जाने से पहले पकड़ सकते हैं। हॉअर की मूल चाल—“क्या शुरू में सच होना चाहिए और क्या बाद में सच होगा”—रिव्यू प्रश्नों में आसानी से बदल जाती है।
जब आप कोई बदलाव पढ़ें, तो हर प्रमुख फ़ंक्शन को एक छोटे वादे के रूप में फ़्रेम करने की कोशिश करें:
एक सरल रिव्यू आदत: अगर आप प्रीकॉन्डिशन/पोस्टकंडीशन एक वाक्य में नहीं कह सकते, तो कोड संभवतः स्पष्ट संरचना की ज़रूरत रखता है।
जोखिम या केंद्रीय फ़ंक्शंस के ऊपर एक छोटा कॉन्ट्रैक्ट कमेंट जोड़ें। इसे ठोस रखें: इनपुट, आउटपुट, साइड-इफेक्ट्स, और त्रुटियाँ।
def withdraw(account, amount):
"""Contract:
Pre: amount is an integer > 0; account is active.
Post (success): returns new_balance; account.balance decreased by amount.
Post (failure): raises InsufficientFunds; account.balance unchanged.
"""
...
ये कमेंट्स औपचारिक प्रमाण नहीं हैं, पर वे रिव्यूअर को स्पष्ट चीज़ें देने के लिए काफी होते हैं जो कोड के खिलाफ जांची जा सकती हैं।
उन कोडों पर अतिरिक्त स्पष्ट रहें जो संभालते हैं:
यदि बदलाव किसी भी इनका स्पर्श करता है, तो पूछें: “प्रीकॉन्डिशन क्या हैं, और उन्हें कहाँ लागू किया गया है?” और “कुछ फेल होने पर हम कौन-सी गारंटी देते हैं?”
औपचारिक रीज़निंग का मतलब यह नहीं कि पुरा कोडबेस गणितीय पेपर बन जाए। लक्ष्य है कि जहाँ अधिक निश्चितता चाहिए वहाँ अतिरिक्त प्रयास लगाएँ: वे हिस्से जहाँ “टेस्ट में ठीक दिखना” पर्याप्त नहीं है।
वे तब सबसे उपयुक्त हैं जब आपके पास एक छोटा, महत्वपूर्ण मॉड्यूल हो जिस पर सब कुछ निर्भर करे (auth, payment rules, permissions, safety interlocks), या एक पेचीदा एल्गोरिथ्म जहाँ ऑफ-बाय-वन गलतियाँ महीनों तक छिपी रहें (parsers, schedulers, caching/eviction, concurrency primitives, partition-style कोड, बाउंडरी-भारी ट्रांसफॉर्म)।
एक उपयोगी नियम: यदि एक बग वास्तविक हानि, बड़ा वित्तीय नुकसान, या चुपचाप डेटा करप्शन पैदा कर सकता है, तो आपको सामान्य रिव्यू + टेस्ट से अधिक चाहिए।
आप “हल्के” से “भारी” तक चुन सकते हैं, और अक्सर सबसे अच्छे नतीजे संयोजन से आते हैं:
फॉर्मैलिटी की गहराई इस पर तौलें:
व्यवहार में, आप “औपचारिकता” को धीरे-धीरे जोड़ सकते हैं: पहले स्पष्ट कॉन्ट्रैक्ट और इनवेरिएंट, फिर ऑटोमेशन से उन्हें बनाए रखें। उदाहरण के लिए Koder.ai जैसी टीमों में—जहाँ React फ्रंट-एंड, Go बैकएंड, और Postgres स्कीमा तीव्र रूप से जनरेट हो सकते हैं—snapshots/rollback और सोर्स-कोड एक्सपोर्ट से आप तेज़ी से इटरेट करते हुए भी कॉन्ट्रैक्ट्स और टेस्ट्स CI में जोड़ सकते हैं।
योजना या कोड रिव्यू में “हमें और औपचारिक बनना चाहिए?” के लिए शीघ्र गेट:
अग्रिम पठन के लिए: design-by-contract, property-based testing, state machines के लिए model checking, आपकी भाषा के लिए static analyzers, और प्रूफ़ असिस्टेंट्स और औपचारिक स्पेसिफ़िकेशन पर परिचयात्मक सामग्री।
सहीपन का मतलब है कि प्रोग्राम एक सहमति-योग्य स्पेसिफ़िकेशन को पूरा करता है: हर स्वीकार्य इनपुट और संबंधित सिस्टम स्थिति के लिए वह अपेक्षित आउटपुट और साइड-इफेक्ट्स देता है (और त्रुटियों को वादे के अनुसार हैंडल करता है)। “ऐसा लगता है काम कर रहा है” आम तौर पर मतलब है आपने केवल कुछ उदाहरण देखे—सभी इनपुट स्पेस या सीमावर्ती स्थितियों की नहीं जाँची।
Requirements व्यवसायिक लक्ष्य होते हैं (“डिस्प्ले के लिए लिस्ट सॉर्ट करो”)। स्पेसिफ़िकेशन वह स्पष्ट, परखा जाने योग्य वादा है (“एक नई लिस्ट लौटाए जो आरोही क्रम में हो, इनपुट के समान मल्टीसेट हो, और इनपुट अपरिवर्तित रहे”)। इम्प्लीमेंटेशन वह कोड है जो लिखा गया। बग अक्सर तब होते हैं जब टीमें requirements से सीधे कोड पर कूद जाती हैं और बीच का परखा जा सकने वाला वादा नहीं लिखतीं।
आंशिक सहीपन: यदि कोड लौटता है तो परिणाम सही है। पूर्ण सहीपन: कोड लौटता है और परिणाम सही है—यानी टर्मिनेशन भी दावे का हिस्सा है.
व्यवहार में, जब “लगातार फंसना” उपयोगकर्ता के लिए विफलता, संसाधन लीक, या सुरक्षा जोखिम बन सकता है, तब पूर्ण सहीपन मायने रखता है।
Hoare ट्रिपल {P} C {Q} को सामान्य भाषा में ऐसे पढ़ें जैसे एक कॉन्ट्रैक्ट:
P (precondition): C चलाने से पहले क्या सच होना चाहिएप्रीकॉन्डिशन वे चीज़ें हैं जो कोड को चलाने से पहले सही होनी चाहिए (उदा., “इन्डेक्स रेंज में हैं”, “एलिमेंट्स कम्पेरेबल हैं”, “लॉक पकड़ा गया है”)। यदि कोई प्रीकॉन्डिशन कॉलर द्वारा तोड़ी जा सकती है, तो या तो:
अन्यथा तुम्हारे पोस्टकंडीशन बस इच्छा बनी रहेंगी।
लूप इनवेरिएंट वह कथन है जो लूप शुरू होने से पहले सच होता है, हर इटरेशन के बाद सच रहता है, और लूप खत्म होने पर भी सच रहता है। उपयोगी टेम्पलेट्स:
0 <= i <= n)यदि आप इनवेरिएंट स्पष्ट नहीं कर पाते, तो यह संकेत है कि लूप बहुत ज़्यादा काम कर रहा है या सीमाएँ अस्पष्ट हैं।
आम तौर पर आप एक माप (variant) नामित करते हैं जो प्रत्येक इटरेशन में घटता है और अनंत तक घट नहीं सकता, जैसे:
n - i हर बार 1 से घटता हैयदि आप घटने वाला माप नहीं ढूँढ पाते, तो आपने असली नॉन-टर्मिनेशन जोखिम खोज लिया हो सकता है (खासकर डुप्लिकेट्स या रुके हुए पॉइंटर्स के साथ)।
Quicksort में partition वह छोटी रूटीन है जिस पर सब कुछ निर्भर करता है। अगर partition थोड़ी भी गलत हो तो आप पा सकते हैं:
इसीलिए partition का कॉन्ट्रैक्ट स्पष्ट रूप से बताना मददगार है: बाएं हिस्से पर क्या सच होगा, दाएँ पर क्या, और यह कि तत्व केवल पुनर्व्यवस्थित हुए हैं (एक परम्यूटेशन)।
डुप्लिकेट्स और pivot के बराबर इलेमेंट्स को कैसे हैंडल करते हैं यह अक्सर फेल होने का कारण है। व्यावहारिक नियम:
अगर डुप्लिकेट्स अक्सर होते हैं, तो three-way partitioning पर विचार करें—यह बग और रेकर्सन गहराई दोनों घटा सकता है।
टेस्टिंग व्यवहारों को पकड़ती है; रीज़निंग (प्रूफ़-स्टाइल) पूरे श्रेणी के बग्स को बाहर कर सकता है (बाउंड्स सुरक्षा, इनवेरिएंट का संरक्षण, टर्मिनेशन)। व्यावहारिक हाइब्रिड वर्कफ़्लो:
सॉर्टिंग के लिए दो उच्च-मूल्य वाली प्रॉपर्टीज़: sortedness (नॉन-डिक्रीज़िंग ऑर्डर) और permutation (इन्स्पुट के समान तत्व और काउंट)।
C: कोड का टुकड़ाQ (postcondition): C खत्म होने के बाद क्या सच होगा, बशर्ते P सत्य थाकोड में यह नोटेशन लिखना जरूरी नहीं—रिव्यू में “इनपुट पर क्या मानकर चल रहे हैं, और बाहर क्या गारंटी देते हैं” यह तरीका ही व्यावहारिक लाभ देता है।