John Ousterhout के विचारों में व्यावहारिक सॉफ़्टवेयर डिज़ाइन, Tcl की विरासत, Ousterhout बनाम Brooks बहस, और कैसे जटिलता उत्पादों को डुबो देती है—इन विषयों की कार्रवाईयोग्य समझ।

John Ousterhout कंप्यूटर वैज्ञानिक और इंजीनियर हैं जिनका काम रिसर्च और असली सिस्टम—दोनों में फैलता है। उन्होंने Tcl प्रोग्रामिंग भाषा बनाई, आधुनिक फ़ाइल सिस्टम को आकार दिया, और बाद में दशकों के अनुभव को एक सरल, थोड़ा असहज दावे में समेटा: सॉफ़्टवेयर का मुख्य दुश्मन जटिलता है।
यह संदेश अभी भी प्रासंगिक है क्योंकि ज़्यादातर टीमें फीचर्स की कमी या मेहनत की कमी के कारण नहीं असफल होती—वे इसलिए असफल होती हैं क्योंकि उनका सिस्टम (और संगठन) समझने में कठिन, बदलने में मुश्किल और टूटने में आसान बन जाता है। जटिलता सिर्फ़ इंजीनियर्स को धीमा नहीं करती। यह उत्पाद निर्णयों, रोडमैप आत्मविश्वास, ग्राहक विश्वास, हादसों की आवृत्ति और यहां तक कि भर्ती में भी रिसाव करती है—क्योंकि ऑनबोर्डिंग महीनों तक चलने वाला कष्ट बन जाता है।
Ousterhout की रूपरेखा व्यावहारिक है: जब कोई सिस्टम विशेष मामलों, अपवादों, छिपी निर्भरताओं और “बस एक बार” सुधारों का संचय कर लेता है, तो लागत सिर्फ़ कोडबेस तक सीमित नहीं रहती। पूरा उत्पाद विकसित होने में महंगा हो जाता है। फीचर देर से बनते हैं, QA मुश्किल होती है, रिलीज़ जोखिम भरे हो जाते हैं, और टीमें सुधारों से बचने लगती हैं क्योंकि किसी भी चीज़ को छूना ख़तरनाक लगता है।
यह अकादमिक शुद्धता की पुकार नहीं है। यह अनुस्मारक है कि हर शॉर्टकट के साथ ब्याज जुड़ जाता है—और जटिलता सबसे उच्च ब्याज़ वाला ऋण है।
इस विचार को ठोस बनाने के लिए हम Ousterhout के संदेश को तीन एंगल्स से देखेंगे:
यह केवल भाषा उत्साही लोगों के लिए नहीं लिखा है। अगर आप उत्पाद बनाते हैं, टीमों का नेतृत्व करते हैं, या रोडमैप ट्रेडऑफ़ करते हैं, तो आप जटिलता को जल्दी पहचानने, उसे संस्थागत होने से रोकने और सादगी को पहली कक्षा की सीमा के रूप में मानने के कार्यवाहीयोग्य तरीके पाएंगे—लॉन्च के बाद की सुख-सुविधा नहीं।
जटिलता “ज़्यादा कोड” या “कठिन गणित” नहीं है। यह उस अंतर को है जो आप सोचते हैं कि सिस्टम एक बदलाव पर क्या करेगा और असल में वह क्या करता है। एक सिस्टम तब जटिल कहलाता है जब छोटे-छोटे edits जोखिमभरे लगते हैं—क्योंकि आप blast radius की भविष्यवाणी नहीं कर सकते।
स्वस्थ कोड में आप यह उत्तर दे सकते हैं: “अगर हम इसे बदलें, तो और क्या टूट सकता है?” जटिलता वही है जो उस प्रश्न को महँगा बनाती है।
यह अक्सर छिपा रहता है:
टीमें जटिलता को महसूस करती हैं जैसे धीमा शिपिंग (जांच में अधिक समय), ज़्यादा बग्स (क्योंकि व्यवहार अचरज में डालता है), और भंगुर सिस्टम (परिवर्तन के लिए कई लोगों और सेवाओं के बीच समन्वय की ज़रूरत)। यह ऑनबोर्डिंग पर भी बोझ डालता है: नए साथी मानसिक मॉडल नहीं बना पाते, इसलिए वे कोर फ़्लो को छूने से बचते हैं।
कुछ जटिलता अनिवार्य है: व्यापार नियम, अनुपालन आवश्यकताएँ, वास्तविक दुनिया के किनारे केस। आप इन्हें हटा नहीं सकते।
पर बहुत कुछ आकस्मिक है: भ्रमित करने वाले APIs, डुप्लिकेट लॉजिक, “अस्थायी” फ्लैग जो स्थायी बन जाते हैं, और मॉड्यूल जो आंतरिक विवरण लीक करते हैं। यही वह जटिलता है जिसे डिज़ाइन विकल्प बनाते हैं—और जिसे आप लगातार घटा सकते हैं।
Tcl की शुरुआत एक व्यावहारिक लक्ष्य के साथ हुई: सॉफ़्टवेयर ऑटोमेशन और मौजूदा एप्लिकेशन्स का विस्तार बिना फिर से लिखा हुए आसान बनाना। John Ousterhout ने इसे इस तरह डिजाइन किया कि टीमें “ठीक उतनी प्रोग्रामैबिलिटी” जोड़ सकें जितनी ज़रूरत हो—और फिर वह शक्ति उपयोगकर्ताओं, ऑपरेटरों, QA या किसी और को दे सकें जिन्हें वर्कफ़्लोज़ स्क्रिप्ट करने की ज़रूरत थी।
Tcl ने एक ग्लू भाषा की अवधारणा को लोकप्रिय किया: एक छोटा, लचीला स्क्रिप्टिंग लेयर जो तेज़, लो-लेवल भाषाओं में लिखे गए कंपोनेंट्स को जोड़ता है। हर फीचर को मोनोलिथ में बनाने के बजाय, आप कमांड्स का सेट एक्सपोज़ कर सकते थे और उन्हें नए व्यवहारों में रच सकते थे।
यह मॉडल प्रभावशाली रहा क्योंकि यह वर्क के असली तरीके से मेल खाता था। लोग सिर्फ उत्पाद ही नहीं बनाते; वे बिल्ड-सिस्टम्स, टेस्ट हार्नेस, एडमिन टूल्स, डेटा कन्वर्टर्स और वन-ऑफ ऑटोमेशन भी बनाते हैं। एक हल्का स्क्रिप्टिंग लेयर इन कार्यों को “टिकट दर्ज करें” से “स्क्रिप्ट लिखें” में बदल देता है।
Tcl ने एम्बेडिंग को प्रथम श्रेणी का विषय बनाया। आप एप्लिकेशन में इंटरप्रेटर डाल सकते थे, एक साफ़ कमांड इंटरफ़ेस एक्सपोज़ कर सकते थे, और तुरंत कॉन्फ़िगरेबिलिटी और तेज़ इटरेशन पा सकते थे।
वही पैटर्न आज भी प्लगइन सिस्टम्स, कॉन्फ़िगरेशन भाषाओं, एक्सटेंशन APIs और एम्बेडेड स्क्रिप्टिंग रनटाइम्स में दिखता है—चाहे स्क्रिप्ट सिंटैक्स Tcl जैसा हो या न हो।
इसने एक महत्वपूर्ण डिज़ाइन आदत को भी मज़बूत किया: स्थिर प्रिमिटिव्स (होस्ट ऐप की कोर क्षमताएँ) को बदलने योग्य composition (स्क्रिप्ट) से अलग रखें। जब यह काम करता है, टूल्स कोर को अक्सर अस्थिर किए बिना तेज़ी से विकसित करते हैं।
Tcl की सिंटैक्स और “सब कुछ स्ट्रिंग है” मॉडल कुछ लोगों को अनइंट्यूटिव लग सकता था, और बड़े Tcl कोडबेस बिना मजबूत कन्वेंशंस के समझने में मुश्किल हो सकते थे। जैसे-जैसे नए पारिस्थितिक तंत्र बेहतर स्टैण्डर्ड लाइब्रेरी, बेहतर टूलिंग और बड़े समुदाय लाए, कई टीमें स्वाभाविक रूप से माइग्रेट हो गईं।
इसका अर्थ यह नहीं कि Tcl की विरासत मिट गई: इसने सामान्य किया कि एक्स्टेंसिबिलिटी और ऑटोमेशन एक्स्ट्रा फीचर नहीं हैं—वे उत्पाद सुविधाएँ हैं जो सिस्टम को उपयोग करने और बनाए रखने वालों के लिए जटिलता को नाटकीय रूप से घटा सकती हैं।
Tcl एक धोखा देने वाली कड़ी सोच पर बना था: कोर छोटा रखें, composition शक्तिशाली बनाएं, और स्क्रिप्ट्स को पठनीय रखें ताकि लोग बिना निरंतर अनुवाद के साथ काम कर सकें।
विशेषीकृत फ़ीचर्स के बड़े सेट के बजाय, Tcl ने प्रिमिटिव्स (स्ट्रिंग्स, कमांड्स, आसान इवैल्युएशन नियम) के एक कॉम्पैक्ट सेट पर भरोसा किया और उम्मीद की कि उपयोगकर्ता उन्हें जोड़ी बनाएँगे।
यह दर्शन डिज़ाइनरों को कम संकल्पनाओं की ओर धकेलता है, जो कई संदर्भों में पुन: उपयोग हो सकती हैं। उत्पाद और API डिज़ाइन के लिए सबक सीधा है: अगर आप दस जरूरतों को दो-तीन सुसंगत बिल्डिंग ब्लॉक्स से हल कर सकते हैं, तो आप उस सतह को घटाते हैं जिसे लोगों को सीखना पड़ता है।
सॉफ़्टवेयर डिज़ाइन का एक मुख्य फंदा बिल्डर की सुविधा के लिए ऑप्टिमाइज़ करना है। एक फीचर इम्प्लीमेंट करने में आसान हो सकता है (मौजूदा विकल्प की नकल करें, एक स्पेशल फ्लैग जोड़ें, एक कॉर्नर केस पैच करें) पर यह उत्पाद को उपयोग करने में मुश्किल बना दे सकता है।
Tcl का ज़ोर ठीक इसका उल्टा था: मानसिक मॉडल को टाइट रखें, भले ही इम्प्लीमेंटेशन को पीछे ज़्यादा काम करना पड़े।
जब आप किसी प्रस्ताव की समीक्षा करें, पूछें: क्या यह उपयोगकर्ता को याद रखने वाली अवधारणाओं की संख्या घटाता है, या क्या यह एक और अपवाद जोड़ता है?
मिनिमलिज़्म तब ही मददगार है जब प्रिमिटिव्स सुसंगत हों। अगर दो कमांड समान दिखते हैं पर किनारे मामलों में अलग व्यवहार करते हैं, तो उपयोगकर्ता ट्रिविया याद करने लगते हैं। छोटे सेट के टूल्स “नुकीले धार” बन जाते हैं जब नियम सूक्ष्म रूप से बदलते हैं।
एक रसोई को सोचें: एक अच्छा चाकू, पैन और ओवन आपको कई पकवान बनाने देते हैं। केवल एवोकाडो काटने वाला गैजेट एक-बार का फीचर है—बेचना आसान, पर दराज को अव्यवस्थित करता है।
Tcl का दर्शन चाकू और पैन के लिए दलील देता है: सामान्य उपकरण जो साफ़ तरीके से मिलते हैं, ताकि हर नए नुस्ख़े के लिए नया गैजेट न चाहिए।
1986 में, Fred Brooks ने एक निबंध लिखा जिसका निहितार्थ ऊधारण था: कोई एकल ब्रेकथ्रू—कोई “सिल्वर बुलेट”—नहीं है जो सॉफ़्टवेयर डेवलपमेंट को एक छलांग में कई गुणा तेज़, सस्ता और अधिक विश्वसनीय बना दे।
उनका बिंदु यह नहीं था कि प्रगति असंभव है। बल्कि यह कि सॉफ़्टवेयर पहले से ही एक ऐसा माध्यम है जिसमें हम लगभग कुछ भी कर सकते हैं, और इस स्वतंत्रता के साथ एक अनूठा बोझ आता है: हम निर्माण करते समय लगातार चीज़ को परिभाषित कर रहे होते हैं। बेहतर टूल मदद करते हैं, पर वे सबसे कठिन हिस्से को मिटा नहीं देते।
Brooks ने जटिलता को दो हिस्सों में बाँटा:
टूल्स आकस्मिक जटिलता को दबा सकते हैं। उच्च-स्तरीय भाषाओं, वर्शन कंट्रोल, CI, कंटेनर, मैनेज्ड डेटाबेस और अच्छे IDEs से हमने क्या पाया है। पर Brooks का तर्क था कि अनिवार्य जटिलता प्रमुख है, और यह सिर्फ़ टूलिंग से गायब नहीं होती।
आधुनिक प्लेटफॉर्म के साथ भी टीमें अभी भी अपनी ऊर्जा का अधिकांश हिस्सा आवश्यकताओं पर बातचीत, सिस्टम एकीकरण, अपवादों को हैंडल करने और समय के साथ व्यवहार बनाए रखने में लगाती हैं। सतह बदल सकती है (क्लाउड APIs बनाम डिवाइस ड्राइवर्स), पर कोर चुनौती वही रहती है: मानव आवश्यकताओं को सटीक, मेंटेनेबल व्यवहार में अनुवाद करना।
यह उस तनाव को सेट करता है जिस पर Ousterhout जोर देते हैं: अगर अनिवार्य जटिलता को मिटाया नहीं जा सकता, क्या अनुशासित डिज़ाइन सचमुच यह घटा सकता है कि कितना हिस्सा कोड और डेवलपर्स के दिमाग में रिसता है?
लोग कभी-कभी “Ousterhout बनाम Brooks” को आशावाद और यथार्थवाद की लड़ाई के रूप में देखते हैं। इसे दो अनुभवी इंजीनियरों के समान समस्या के अलग हिस्सों को बताने वाला पढ़ना ज़्यादा उपयोगी है।
Brooks का “No Silver Bullet” तर्क है कि कोई एक ब्रेकथ्रू नहीं है जो जटिलता को जादुई रूप से हटा देगा। Ousterhout इस बात का अस्वीकार नहीं करता।
उनका तर्क संकीर्ण और व्यावहारिक है: टीमें अक्सर जटिलता को अनिवार्य मान लेती हैं जबकि इसका बड़ा हिस्सा स्व-निर्मित है।
Ousterhout के दृष्टिकोण में, अच्छा डिज़ाइन जटिलता को अर्थपूर्ण ढंग से घटा सकता है—न यह कहकर कि सॉफ़्टवेयर “आसान” हो जाएगा, बल्कि इस तरह कि इसे बदलना कम भ्रमित करने वाला बने। यह बड़ा दावा है, और मायने रखता है क्योंकि भ्रम रोज़मर्रा के काम को धीमा कर देता है।
Brooks ने जिस अनिवार्य कठिनाई पर ज़ोर दिया वह है: सॉफ़्टवेयर को गंदी वास्तविकताओं, बदलती आवश्यकताओं, और बाहरी किनारे मामलों को मॉडल करना पड़ता है। अच्छे टूल और स्मार्ट लोग होने पर भी आप इसे मिटा नहीं सकते—आप इसे सिर्फ़ मैनेज कर सकते हैं।
वे जितना बहस दिखाते हैं उससे अधिक ओवरलैप करते हैं:
“कौन सही है?” पूछने की बजाय पूछें: इस तिमाही में हम कौन सी जटिलता पर नियंत्रण कर सकते हैं?
टीमें बाज़ार के बदलाव या डोमेन की मूल कठिनाई को नियंत्रित नहीं कर सकतीं। पर वे नियंत्रित कर सकती हैं कि नए फ़ीचर विशेष मामले जोड़ते हैं या नहीं, क्या APIs कॉलर्स को छिपे नियम याद रखने पर मजबूर करते हैं, और क्या मॉड्यूल जटिलता छिपाते हैं या लीक करते हैं।
यह कार्रवाईयोग्य मध्यम रास्ता है: अनिवार्य जटिलता स्वीकार करें, और आकस्मिक प्रकार के बारे में निर्दयता से चयन करें।
एक डीप मॉड्यूल वह कंपोनेंट है जो बहुत कुछ करता है, जबकि एक छोटा और समझने में आसान इंटरफ़ेस एक्सपोज़ करता है। “गहराई” उस जटिलता की मात्रा है जो मॉड्यूल आपकी थाली से हटाता है: कॉलर्स को गंदे विवरण जानने की ज़रूरत नहीं होती, और इंटरफ़ेस उन्हें मजबूर नहीं करता।
एक शैलो मॉड्यूल इसका उल्टा है: यह शायद थोड़ी लॉजिक लपेटेगा, पर यह जटिलता को बाहर धकेल देगा—कई पैरामीटर, स्पेशल फ्लैग्स, आवश्यक कॉल ऑर्डर, या “आपको याद रखना होगा…” नियमों के ज़रिये।
एक रेस्टोरेंट सोचें। एक डीप मॉड्यूल रसोई है: आप एक सरल मेनू से “पास्ता” ऑर्डर करते हैं और सप्लायर विकल्प, उबालने का समय या प्लेटिंग की परवाह नहीं करते।
एक शैलो मॉड्यूल वह “रसोई” है जो आपको कच्चे घटक और 12-स्टेप निर्देश देती है और आपकी पैन लाने को कहती है। काम अभी भी होता है—पर यह ग्राहक पर स्थानांतरित हो गया है।
अतिरिक्त लेयर्स तब बढ़िया होती हैं जब वे कई निर्णयों को एक स्पष्ट विकल्प में समेटती हैं।
उदाहरण के लिए, एक स्टोरेज लेयर जो save(order) एक्सपोज़ करती है और अंदर retries, serialization, और indexing संभालती है तो वह डीप है।
लेयर्स तब हानिकारक होती हैं जब वे ज्यादातर चीज़ों का नाम बदलती हैं या विकल्प जोड़ती हैं। अगर नई एब्स्ट्रैक्शन अधिक कॉन्फ़िगरेशन जोड़ती है जितना हटाती है—जैसे save(order, format, retries, timeout, mode, legacyMode)—तो यह शायद शैलो है। कोड “संगठित” लग सकता है, पर संज्ञानात्मक भार हर कॉल साइट पर दिखेगा।
useCache, skipValidation, force, legacy।डीप मॉड्यूल्स सिर्फ़ कोड को एनकैप्सुलेट नहीं करते। वे फ़ैसलों को एनकैप्सुलेट करते हैं।
“अच्छा” API सिर्फ़ ज्यादा कर सकने वाला नहीं होता। वह ऐसा API है जिसे लोग काम करते हुए दिमाग में रख सकें।
Ousterhout का डिज़ाइन लेंस आपको API को मानसिक प्रयास के हिसाब से आँकने के लिए कहता है: आपको कितने नियम याद रखने हैं, कितने अपवाद की भविष्यवाणी करनी है, और गलती से गलत करने की कितनी संभावना है।
मानव-अनुकूल APIs अक्सर छोटे, सुसंगत और गलत-उपयोग के लिए कठिन होते हैं।
छोटा होने का मतलब कमजोर नहीं—बल्कि सतह क्षेत्र कई अवधारणाओं में केंद्रीकृत होना चाहिए जो अच्छी तरह से संयोजित हों। सुसंगतता का मतलब है कि वही पैटर्न पूरे सिस्टम में काम करे (पैरामीटर, एरर हैंडलिंग, नामकरण, रिटर्न टाइप)। गलत-उपयोग के लिए कठिन होने का मतलब है कि API आपको सुरक्षित रास्तों की ओर मार्गदर्शित करे: बॉउंडरी पर सत्यापन, स्पष्ट इनवैरीअंट्स, और प्रकार या रनटाइम चेक जो जल्दी फेल हों।
हर अतिरिक्त फ्लैग, मोड या “बस-इमरजेंसी-केसेस” कॉन्फ़िगरेशन सभी उपयोगकर्ताओं पर कर बन जाता है। भले ही केवल 5% कॉलर्स को इसकी ज़रूरत हो, 100% कॉलर्स को अब यह सीखना होगा कि यह मौजूद है, यह सोचने पर मजबूर होगा कि क्या उन्हें इसकी ज़रूरत है, और इंटरैक्शन में मिलने पर व्यवहार की व्याख्या करनी होगी।
यही तरीका है कि APIs छिपी जटिलता इकट्ठा करते हैं: किसी एक कॉल में नहीं, बल्कि संयोजनों में।
डिफ़ॉल्ट्स एक दया हैं: वे अधिकांश कॉलर्स को निर्णय छोड़ने और फिर भी समझदारीपूर्ण व्यवहार पाने देते हैं। कन्वेंशंस (एक स्पष्ट तरीका) उपयोगकर्ता के मन में शाखाओं को घटाते हैं। नामकरण असली काम करता है भी: क्रियाएँ और संज्ञाएँ उपयोगकर्ता इरादे के अनुरूप रखें, और समान ऑपरेशन्स के नाम मिलते-जुलते रखें।
एक और याद: आंतरिक APIs उतने ही मायने रखते हैं जितने सार्वजनिक ones। उत्पाद की अधिकांश जटिलता पर्दे के पीछे रहती है—सर्विस बाउंडरीज़, साझा लाइब्रेरीज़, और “हेल्पर” मॉड्यूल। उन इंटरफेस को एक उत्पाद की तरह ट्रीट करें, रिव्यू और वर्शनिंग अनुशासन के साथ (देखें भी /blog/deep-modules)।
जटिलता शायद ही कभी एक ही “बुरा निर्णय” के रूप में आती है। यह छोटे, तार्किक दिखने वाले पैचों के जरिए जमा होती है—खासकर जब टीमें डेडलाइन दबाव में हों और तत्काल लक्ष्य शिप करना हो।
एक फंदा है हर जगह फीचर फ्लैग्स। फ्लैग्स सुरक्षित रोलआउट के लिए उपयोगी हैं, पर जब वे लंबे समय तक रहते हैं, तो हर फ्लैग व्यवहारों की संभावनाओं की संख्या गुणा कर देता है। इंजीनियर्स “सिस्टम” के बारे में सोचना बंद कर देते हैं और “सिस्टम, सिवाय जब फ्लैग A ऑन हो और यूजर सेगमेंट B में हो” के बारे में सोचने लगते हैं।
एक और है स्पेशल-केस लॉजिक: “एंटरप्राइज़ कस्टमर्स को X चाहिए,” “क्षेत्र Y में छोड़कर,” “जब अकाउंट 90 दिनों से पुराना हो।” ये अपवाद अक्सर कोडबेस में फैलते हैं, और कुछ महीनों बाद कोई नहीं जानता कि कौन सा अभी भी जरूरी है।
तीसरा है लीकी एब्स्ट्रैक्शन्स। एक API जो कॉलर्स को आंतरिक विवरण (टाइमिंग, स्टोरेज फॉर्मैट, कैशिंग नियम) समझने के लिए मजबूर करता है, जटिलता को बाहर धकेल देता है। एक मॉड्यूल की जगह कई कॉलर्स के पास quirks होते हैं।
सामरिक प्रोग्रामिंग इस सप्ताह के लिए ऑप्टिमाइज़ करती है: तेज़ फिक्स, न्यूनतम बदलाव, “शिप कर दो।”
रणनीतिक प्रोग्रामिंग अगले वर्ष के लिए ऑप्टिमाइज़ करती है: छोटे redesigns जो एक ही प्रकार की बग्स को रोकते हैं और भविष्य के काम को घटाते हैं।
खतरा है “रख-रखाव ब्याज।” एक तात्कालिक वर्कअराउंड अभी सस्ता लगता है, पर आप उसे ब्याज के साथ लौटाते हैं: धीमा ऑनबोर्डिंग, भंगुर रिलीज़, और डर पर आधारित विकास जहाँ कोई भी पुराने कोड को छूना नहीं चाहता।
कोड रिव्यू में हल्के-फुल्के संकेत जोड़ें: “क्या यह नया स्पेशल केस जोड़ता है?” “क्या API यह विवरण छिपा सकता है?” “हम किन जटिलताओं को पीछे छोड़ रहे हैं?”
गैर-तरीकेदार फैसलों के लिए छोटे निर्णय रिकॉर्ड रखें (कुछ बुलेट काफी हैं)। और हर स्प्रिंट में एक छोटा रीफैक्टर बजट रिज़र्व रखें ताकि रणनीतिक फिक्स को अतिरिक्त काम न माना जाए।
जटिलता इंजीनियरिंग में फँसकर नहीं रहती। यह शेड्यूल, विश्वसनीयता, और ग्राहकों के अनुभव में रिसाव कर देती है।
जब सिस्टम समझने में कठिन होता है, हर बदलाव ज्यादा समय लेता है। टाइम-टू-मार्केट स्लिप होता है क्योंकि हर रिलीज़ में अधिक समन्वय, अधिक रिग्रेशन टेस्टिंग, और अधिक “सुरक्षा के लिए” रिव्यू चक्र लगते हैं।
विश्वसनीयता भी प्रभावित होती है। जटिल सिस्टम ऐसे इंटरैक्शन्स पैदा करते हैं जिन्हें कोई पूरी तरह भविष्यवाणी नहीं कर सकता, इसलिए बग किनारे मामलों के रूप में आते हैं: चेकआउट तभी फेल होता है जब कूपन, सेव्ड कार्ट, और क्षेत्रीय कर नियम किसी विशेष तरीके से मिलते हैं। ये वे incidents हैं जिन्हें reproduce करना कठिन और fix करना धीमा होता है।
ऑनबोर्डिंग एक छिपा हुआ ड्रैग बन जाता है। नए साथी उपयोगी मानसिक मॉडल नहीं बना पाते, इसलिए वे जोखिमभरे हिस्सों को छूने से बचते, उन पैटर्न्स की नकल करते जिन्हें वे नहीं समझते, और अनजाने में और अधिक जटिलता जोड़ देते हैं।
ग्राहकों को परवाह नहीं कि व्यवहार “कोड में स्पेशल केस” के कारण है। वे इसे असंगति के रूप में अनुभव करते हैं: सेटिंग्स हर जगह लागू नहीं होतीं, फ़्लो उस तरीके से बदलते हैं जिससे आप आए थे, फीचर्स “ज्यादातर समय” काम करते हैं।
विश्वास घटता है, churn बढ़ता है, और अपनाना रुक जाता है।
सपोर्ट टीमें जटिलता का भुगतान लंबी टिकटों और संदर्भ इकट्ठा करने में अधिक बैक-एंड-फोर्थ के रूप में करती हैं। ऑपरेशंस अधिक अलर्ट, अधिक रनबुक्स, और अधिक सावधान डिप्लॉयमेंट्स के रूप में भुगतान करता है। हर अपवाद कुछ मॉनिटर, डॉक्युमेंट और समझाने जैसा बन जाता है।
कल्पना करें कि “एक और नोटिफिकेशन रूल” के लिए अनुरोध आ रहा है। इसे जोड़ना तेज़ लगता है, पर यह व्यवहार में एक और शाखा जोड़ता है, UI कॉपी बढ़ती है, टेस्ट केस बढ़ते हैं, और उपयोगकर्ता गलत कॉन्फ़िग कर सकता है।
अब तुलना करें कि मौजूदा नोटिफिकेशन फ्लो को सरल बनाना: कम रूल टाइप, स्पष्ट डिफ़ॉल्ट्स, और वेब व मोबाइल में सुसंगत व्यवहार। आप कम नॉब्स शिप कर सकते हैं, पर आप आश्चर्यों को घटाते हैं—जिससे उत्पाद उपयोग में आसान, सपोर्ट में आसान, और विकसित करने में तेज़ हो जाता है।
जैसे प्रदर्शन या सुरक्षा को आप योजना बनाते, मापते और संरक्षित करते हैं, वैसे ही जटिलता को भी ट्रीट करें। अगर आप केवल तब जटिलता नोटिस करते हैं जब डिलीवरी धीमी हो जाती है, तो आप पहले से ही ब्याज़ चुका रहे हैं।
फीचर स्कोप के साथ-साथ यह परिभाषित करें कि किसी रिलीज़ में कितनी नई जटिलता जोड़ी जा सकती है। बजट सरल हो सकता है: “कोई नया कॉन्सेप्ट नहीं जब तक हम एक नहीं हटाते,” या “कोई नया इंटीग्रेशन तभी जोड़ा जाए जब वह पुरानी राह को बदल दे।”
योजना में ट्रेडऑफ़्स को स्पष्ट रखें: अगर एक फीचर तीन नए कॉन्फ़िग मोड और दो अपवाद केस माँगता है, तो उसे ऐसे फीचर की तुलना में ज़्यादा “लागत” देनी चाहिए जो मौजूदा अवधारणाओं में फिट बैठता है।
पूर्ण-सटीक नंबर की ज़रूरत नहीं—सिर्फ संकेत जो सही दिशा में ट्रेंड करते हों:
इन्हें प्रति रिलीज़ ट्रैक करें, और निर्णयों से बाँधें: “हमने दो नए सार्वजनिक विकल्प जोड़े; हमने प्रतिसंस्थापित करने के लिए क्या हटाया या सरल किया?”
प्रोटोटाइप अक्सर इस आधार पर आँके जाते हैं: “क्या हम इसे बना सकते हैं?” इसके बजाय उन्हें यह जवाब देने के लिए उपयोग करें: “क्या यह उपयोग में सरल महसूस होता है और गलत इस्तेमाल के लिए कठिन है?”
किसी अनजान व्यक्ति को प्रोटोटाइप देकर एक वास्तविक टास्क कराएँ। समय-से-सफलता, पूछे गए प्रश्न और जहाँ वे गलत अनुमानों पर पहुँचते हैं, उन्हें मापें। वे ही जटिलता हॉटस्पॉट होते हैं।
यहाँ आधुनिक बिल्ड वर्कफ़्लोज़ आकस्मिक जटिलता को घटा सकते हैं—यदि वे iteration को तंग रखें और गलतियों को जल्दी reset करना आसान बनाएं। उदाहरण के लिए, जब टीमें आंतरिक टूल या नए फ्लो का स्केच बनाने के लिए चैट के माध्यम से Koder.ai जैसे प्लेटफ़ॉर्म का उपयोग करती हैं, तो planning mode (जनरेशन से पहले इरादे साफ़ करने के लिए) और snapshots/rollback (जोखिमभरे बदलाव जल्दी undo करने के लिए) जैसी सुविधाएँ शुरुआती प्रयोग को सुरक्षित महसूस करा सकती हैं—बिना आधे-तैयार एब्स्ट्रैक्शन्स के ढेर के। अगर प्रोटोटाइप सफल होता है, तो आप सोर्स कोड एक्सपोर्ट कर सकते हैं और ऊपर वर्णित “डीप मॉड्यूल” और API अनुशासन लागू कर सकते हैं।
“जटिलता क्लीनअप” का काम त्रैमासिक या हर प्रमुख रिलीज़ के साथ नियमित करें, और परिभाषित करें कि “हो गया” का मतलब क्या है:
लक्ष्य केवल साफ़ कोड नहीं है—बल्कि कम अवधारणाएँ, कम अपवाद, और सुरक्षित परिवर्तन है।
नीचे कुछ कदम हैं जो Ousterhout के “जटिलता दुश्मन है” विचार को सप्ताह-दर-सप्ताह टीम आदतों में बदलते हैं।
एक ऐसा सबसिस्टम चुनें जो नियमित रूप से भ्रम पैदा करता हो (ऑनबोर्डिंग दर्द, बार-बार होने वाले बग्स, “यह कैसे काम करता है?” सवालों की संख्या अधिक)।
आंतरिक फॉलो-अप जो आप चला सकते हैं: योजना में एक “complexity review” (/blog/complexity-review) और एक त्वरित जाँच कि आपकी टूलिंग आकस्मिक जटिलता घटा रही है या नए लेयर्स जोड़ रही है (/pricing)।
अगर आप केवल इस सप्ताह एक ही स्पेशल केस हटा सकते, तो आप सबसे पहले कौन सी जटिलता हटाना चाहेंगे?
Complexity उस अंतर को बताती है जो आप जो उम्मीद करते हैं और सिस्टम पर बदलाव करने पर असल में क्या होता है, उसके बीच मौजूद है.
आप इसे तब महसूस करते हैं जब छोटे बदलाव जोखिमभरे लगते हैं क्योंकि आप यह नहीं बता पाते कि उनका प्रभाव कहाँ-कहाँ पड़ेगा (टेस्ट, सेवाएँ, कॉन्फ़िग, ग्राहक या एज़ केस जो टूट सकते हैं)।
निम्न संकेत देखें जो बताते हैं कि reasoning महंगा हो रहा है:
Essential complexity डोमेन से आती है (नियम, असली दुनिया के बिंदु, व्यापार नियम)। इसे मिटाया नहीं जा सकता—सिर्फ़ अच्छा मॉडल किया जा सकता है।
Accidental complexity स्वयं पैदा की गई है (लीकी एब्स्ट्रैक्शन्स, डुप्लिकेट लॉजिक, बहुत सारे मोड/flags, अस्पष्ट APIs)। इसे डिज़ाइन और सरलीकरण के ज़रिये घटाया जा सकता है।
एक deep module बहुत कुछ internally संभालता है और एक छोटा, स्थिर इंटरफ़ेस एक्सपोज़ करता है। यह messy_DETAILS (रिट्राई, फॉर्मैट, ऑर्डरिंग, इनवैरीअंट्स) को कॉलर से छिपाता है।
एक व्यवहारिक टेस्ट: यदि अधिकतर कॉलर इंटरनल नियमों को जाने बिना मॉड्यूल का सही उपयोग कर सकते हैं तो वह deep है; अगर कॉलरों को नियम और अनुक्रम याद रखने पड़ते हैं तो वह shallow है।
सामान्य लक्षण:
legacy, skipValidation, force, mode).इसके लिए APIs को प्राथमिकता दें जो:
जब आप एक और ऑप्शन जोड़ने के बारे में सोचें, पहले पूछें: क्या हम इंटरफ़ेस इस तरह redesign कर सकते हैं कि अधिकांश कॉलरों को उस चुनाव के बारे में सोचना न पड़े।
फीचर फ्लैग्स को controlled rollout के लिए उपयोग करें, फिर इन्हें डेटेड टेक्निकल देब्ट की तरह ट्रीट करें:
लंबे समय तक रहने वाली फ्लैग्स इंजीनियर्स को सोचने वाली संभावित प्रणालियों की संख्या बढ़ा देती हैं।
योजना में जटिलता को स्पष्ट करें, सिर्फ़ कोड रिव्यू तक सीमित न रखें:
उद्देश्य यह सुनिश्चित करना है कि ट्रेडऑफ़्स जटिलता संस्थागत होने से पहले स्पष्ट हों।
Tcl से मिलने वाला स्थायी सबक है: थोड़े सिद्धांतों का सेट और मजबूत composition की शक्ति—अक्सर एक embedded “glue” लेयर के रूप में।
आधुनिक समकक्षों में शामिल हैं:
डिज़ाइन लक्ष्य वही है: कोर को सरल व स्थिर रखें, और बदलाव साफ़ इंटरफेस के ज़रिये होने दें।
शैलो मॉड्यूल अक्सर व्यवस्थित दिखते हैं पर वास्तविक जटिलता हर कॉलर पर सरक जाती है।