- जटिलता डेवलपमेंट में सबसे ख़तरनाक तत्व है
- असली दक्षता "80/20 solution" जैसे व्यावहारिक अप्रोच से आती है जो जटिलता से बचते हैं
- टेस्टिंग और रिफैक्टरिंग के प्रति संतुलित और लचीला रवैया बनाए रखना महत्वपूर्ण है
- टूल्स के उपयोग और पढ़ने व मेंटेन करने में आसान कोड लिखने की आदत अपनाने पर ज़ोर दिया गया है
- अत्यधिक abstraction और ट्रेंड्स से सावधान रहने तथा सादगी को अपनाने की सलाह दी गई है
परिचय
- यह लेख लंबे समय तक सॉफ़्टवेयर बनाते हुए अनुभव से सीखी गई बातों को समेटने वाले Grug Brain डेवलपर के विचारों का संग्रह है
- Grug Brain डेवलपर खुद को बहुत स्मार्ट नहीं मानता, लेकिन लंबे समय तक प्रोग्रामिंग करते हुए उसने बहुत कुछ सीखा है
- वह चाहता है कि दूसरे लोग गलतियों से सीखें, इसलिए अपनी समझ को आसान और मज़ेदार तरीके से साझा करता है
- जटिलता ही डेवलपर जीवन की सबसे बड़ी दुश्मन है
- जटिलता कोडबेस में चुपके से घुस जाती है, और जो कोड शुरू में समझने में आसान था, उसे धीरे-धीरे बदलना लगभग असंभव बना देती है
जटिलता के दानव से निपटना
- जटिलता एक अदृश्य आत्मा की तरह बिना शोर किए भीतर घुसती है, और project manager तथा non-Grug डेवलपर अक्सर इसे ठीक से पहचान नहीं पाते
- जटिलता को रोकने का सबसे अच्छा तरीका है "नहीं" कहना
- "मैं यह feature नहीं बनाऊँगा"
- "मैं यह abstraction नहीं लाऊँगा"
- बेशक, करियर के हिसाब से ज़ोर से "हाँ" कहना ज़्यादा फ़ायदेमंद हो सकता है, लेकिन Grug Brain डेवलपर अपने प्रति ईमानदार विकल्प को महत्व देता है
- परिस्थितियों के अनुसार समझौता ("ok") भी ज़रूरी है, और ऐसे मामलों में वह 80/20 solution (Pareto law) से समस्या को सरल तरीके से हल करना पसंद करता है
- project manager को सब कुछ बताए बिना, असल में 80/20 तरीके से काम पूरा कर देना भी एक समझदार रणनीति है
कोड संरचना और abstraction
- कोड की सही इकाइयाँ (cut points) समय के साथ स्वाभाविक रूप से सामने आती हैं, इसलिए शुरुआती abstraction से बचना बेहतर है
- अच्छा cut point वह है जिसका बाकी सिस्टम के साथ interface संकरा हो
- जल्दी abstraction करने की कोशिश अक्सर विफल होती है, और अनुभवी डेवलपर कोड का आकार कुछ स्थिर होने के बाद धीरे-धीरे संरचना बनाते हैं
- कम अनुभवी या “big brain” डेवलपर प्रोजेक्ट की शुरुआत में ही ज़रूरत से ज़्यादा abstraction करने लगते हैं, जिससे maintenance का बोझ रह जाता है
टेस्ट रणनीति
- टेस्टिंग के प्रति लगाव और संतुलन दोनों महत्वपूर्ण हैं
- वह prototyping के बाद, जब कोड कुछ हद तक स्थिर हो जाए, तब टेस्ट लिखना पसंद करता है
- unit test शुरुआती चरण में काम आते हैं, लेकिन व्यवहार में बीच का स्तर (integration test) सबसे ज़्यादा असरदार होता है
- end-to-end test भी ज़रूरी हैं, लेकिन अगर बहुत ज़्यादा हों तो maintenance असंभव हो सकता है, इसलिए केवल आवश्यक paths की सीमित संख्या रखनी चाहिए
- bug report आने पर, bug ठीक करने से पहले उसका reproduction test ज़रूर जोड़ा जाना चाहिए
प्रोसेस, Agile, रिफैक्टरिंग
- Agile Grug डेवलपर के लिए बुरा नहीं है, और सबसे ख़राब भी नहीं, लेकिन "Agile shaman" से बहुत ज़्यादा उम्मीद रखना ख़तरनाक है
- prototyping, टूल्स और अच्छे सहकर्मी वास्तव में ज़्यादा महत्वपूर्ण सफलता कारक हैं
- refactoring भी अच्छी आदत है, लेकिन बड़ा और ज़बरदस्ती किया गया refactoring ख़तरनाक होता है
- जटिल abstraction को ज़बरदस्ती लाना उल्टा प्रोजेक्ट की विफलता का कारण बन सकता है
मेंटेनेंस, पूर्णतावाद और विनम्रता
- मौजूदा सिस्टम को बिना वजह उखाड़-पछाड़ करना ख़तरनाक है, और “यह संरचना क्यों है, पता नहीं” कहकर उसे बस हटा देना अच्छी आदत नहीं है
- परफ़ेक्ट कोड का सपना देखने वाला आदर्शवाद, व्यवहार में, अधिकतर समस्याएँ पैदा करता है
- अनुभव बढ़ने के साथ यह गहराई से महसूस होता है कि “जो कोड काम कर रहा है, उसका सम्मान” करना चाहिए
टूल्स और उत्पादकता
- अच्छे डेवलपमेंट टूल्स (IDE code completion, debugger आदि) उत्पादकता को बहुत बढ़ाते हैं, और उन्हें गहराई से समझना महत्वपूर्ण है
- type system की असली क़ीमत “auto-complete” और गलती से बचाव में है, और अत्यधिक abstraction तथा generics उल्टा ख़तरनाक हो सकते हैं
कोड स्टाइल और दोहराव
- ज़्यादा पढ़ने योग्य और debug करने में आसान कोड के लिए, condition expressions को कई लाइनों में बाँटने जैसी शैली की सिफ़ारिश की गई है
- DRY(Don’t Repeat Yourself) सिद्धांत का सम्मान किया जाता है, लेकिन बार-बार आने वाले कोड को ज़बरदस्ती हटाने से ज़्यादा संतुलन महत्वपूर्ण है
- कई स्थितियों में साधारण दोहराव, जटिल DRY implementation से बेहतर होता है
सॉफ़्टवेयर डिज़ाइन सिद्धांत
- SoC(Separation of Concerns) सिद्धांत से ज़्यादा behavior locality को पसंद किया जाता है, और यह तर्क दिया जाता है कि “जो कोड वह व्यवहार करता है, वही उस object में होना चाहिए ताकि maintenance आसान रहे”
- callback/closure, type system, generics, abstraction आदि का सीमित और उचित उपयोग करने की चेतावनी दी गई है
- closure का ज़रूरत से ज़्यादा उपयोग JavaScript में "callback hell" बना सकता है
लॉगिंग, संचालन
- logging बहुत महत्वपूर्ण है; हर प्रमुख branch पर इसे छोड़ा जाना चाहिए, और cloud environment में request ID आदि के ज़रिए trace किया जा सके, ऐसा सेटअप होना चाहिए
- dynamic log level और user-specific log का उपयोग कर सकें, तो चल रहे सिस्टम में समस्या ट्रैक करने में बहुत मदद मिलती है
concurrency, optimization
- concurrency में केवल अधिकतम सरल मॉडल (stateless web requests, अलग worker queue आदि) पर भरोसा किया जाता है
- optimization केवल तब करने की सलाह दी जाती है जब वास्तविक performance profile data उपलब्ध हो
- network I/O जैसे छिपे हुए costs पर ध्यान देना चाहिए; केवल CPU complexity देखकर निर्णय लेना ख़तरनाक है
API डिज़ाइन
- अच्छा API इस्तेमाल में आसान होना चाहिए, और बहुत जटिल डिज़ाइन या abstraction डेवलपर अनुभव को नुकसान पहुँचाते हैं
- "use case के अनुरूप simple API" और "जटिल case भी implement किए जा सकें ऐसा layered API" ढाँचा सुझाया गया है
parser डेवलपमेंट
- recursive descent parser को अकादमिक दुनिया में कम आंका जाता है, लेकिन वास्तविक production code के लिए यह सबसे उपयुक्त और सबसे आसानी से समझ आने वाला तरीका है
- parser डेवलपमेंट के अधिकतर अनुभव के अनुसार, tools से generate किए गए parser इतने जटिल हो जाते हैं कि समस्या हल करने में उल्टा नुकसान करते हैं
- सुझाई गई किताबों में "Crafting Interpreters" को सर्वश्रेष्ठ बताया गया है, और इसमें बहुत सी व्यावहारिक सलाह है
फ्रंटएंड और रुझान
- modern frontend (React, SPA, GraphQL आदि) उल्टा जटिलता के दानव को और बुलाते हैं, और कई बार अनावश्यक होते हैं
- Grug खुद htmx, hyperscript जैसे सरल टूल्स के ज़रिए जटिलता घटाने का तरीका पसंद करता है
- frontend में लगातार नए प्रयोग होते रहते हैं, लेकिन यह ध्यान रखना चाहिए कि उनमें पुराने विचारों की पुनरावृत्ति भी बहुत होती है
मनोवैज्ञानिक पहलू, imposter syndrome
- ज़्यादातर डेवलपर कई बार ऐसा महसूस करते हैं कि “मुझे नहीं पता मैं क्या कर रहा हूँ”, और FOLD(Fear Of Looking Dumb) जैसी स्थिति से मुक्त होने की ज़रूरत है
- जब senior डेवलपर खुलकर कहते हैं कि “यह मुझे भी कठिन लगता है, यह बहुत जटिल है”, तो junior डेवलपर भी दबाव कम महसूस कर सकते हैं
- imposter syndrome एक आम भावना है, और सीखते हुए आगे बढ़ना पूरी तरह संभव है—यह बात प्रोत्साहित की गई है
निष्कर्ष
- प्रोग्रामिंग में जटिलता से हमेशा सावधान रहना चाहिए, और सादगी बनाए रखना सफल डेवलपमेंट की कुंजी है
- अनुभव, टूल्स का प्रभावी उपयोग, विनम्रता, और वास्तव में काम करने वाले कोड का सम्मान लंबे समय में अधिक दक्ष और मूल्यवान डेवलपमेंट की ओर ले जाते हैं
- "जटिलता बहुत, बहुत बुरी है"—इस वाक्य को हमेशा याद रखना चाहिए
1 टिप्पणियां
Hacker News राय
printstatements से debugging करते हैं। मैं अपना workflow साथियों को बताने की कोशिश करता हूँ, लेकिन कोई खास प्रतिक्रिया नहीं मिलती। मैं सहमत हूँ कि किसी system को समझने की सबसे अच्छी शुरुआत debugger से होती है। टेस्ट के दौरान किसी दिलचस्प code line पर रुककर stack देखना, दिमाग में code को ट्रेस करने से कहीं आसान है। अगर debugger इस्तेमाल करना सीख लिया जाए, तो सच में एक छोटा-सा superpower मिल जाता है। अगर संभव हो तो इसे ज़रूर आज़माएँprintdebugging ही एकमात्र संभव विकल्प बनती है। यहाँ तक कि अगर log system में भी समस्या हो, या program log छापने से पहले ही crash हो जाए, तोprintभी काम नहीं आताprintoutput व self-checking code जोड़ना, ज़्यादा productive होता है।printडालना debugger में step-by-step जाने से कहीं तेज़ है। औरprintcode program में रह जाता है, जबकि debugging session गायब हो जाता है।" मैं भी इस राय से सहमत हूँ। development के ज़्यादातर हिस्से मेंprint-hypothesis-run loop कहीं तेज़ problem solving देता है। मैं code को दिमाग में "चलाता" नहीं, बल्कि flow का एक working model पहले से होता है, इसलिए जबprintगलत output दिखाता है तो ज़्यादातर बार असली स्थिति का अंदाज़ा जल्दी हो जाता है। संबंधित लिंक: The unreasonable effectiveness of print debuggingprintfdebugging हमेशा आम रहने का एक कारण यह भी था कि GUI-based debuggers पर भरोसा नहीं किया जा सकता था। Linux का GUI अक्सर अस्थिर रहता है। मेरे लिए भी debugger को ठीक से इस्तेमाल करना तब शुरू हुआ जब (1) Windows में GUI अच्छा चलता था लेकिन CLI अक्सर टूट जाता था, और (2)printdebugging code गलती से version में commit होकर कई बार समस्या पैदा कर चुका था। उसके बाद मैंने CLI debugger के साथ कई प्रयोग किए, और Junit+debugger (जैसे Eclipse जैसे IDE आधारित) के साथ experimental code तुरंत लिखकर उसे test के रूप में छोड़ देने वाली प्रक्रिया मुझे Python REPL जितनी सुविधाजनक लगी। हाँ, debugger को environment के हिसाब से सेट करने के लिए शुरुआती investment चाहिएAstWalker,AstItem::dispatch(AstWalker),AstWalker::process(AstItem)जैसे ठोस नाम कहीं अधिक अर्थपूर्ण लगते हैं। visitor यानी “मुलाक़ात करने वाला” इतना abstract और अर्थहीन है कि उससे मदद नहीं मिलती। संदर्भ के अनुसार नाम अलग होना चाहिए, और चाहें तो comment में बस ‘visitor pattern’ लिख देने से पहचान में कोई समस्या नहीं होती। अतीत में मुझे दो object trees को match करके data compare/import करना था, तब मैंनेAbstractImporterनाम इस्तेमाल किया था। वह ज़्यादा specific था, process और role दोनों स्पष्ट थे। वह typical visitor pattern से अलग था