- पैकेज इंस्टॉल करना डिफ़ॉल्ट बन चुकी डेवलपमेंट संस्कृति अपडेट, patch, audit और transitive dependency management से जुड़ी dependency churn को बढ़ाती है, जिससे productivity पर छिपी हुई लागत आती है
- JavaScript और Rust जैसे अच्छी तरह packaged ecosystems में इसका असर और बड़ा है; उदाहरण के तौर पर नया Tokio project 28 crate, Rocket 172 crate, और MiniJinja CLI 142 dependencies लाता है
terminal_size जैसी लंबे समय से स्थिर functionality में भी platform abstraction libraries के बदलावों की वजह से अतिरिक्त crates और बार-बार releases का विरोधाभास पैदा होता है
- छोटी functionality के लिए ChatGPT या Cursor से dependency-free implementation बनवाना dependency खोजने और लगातार upgrades करने से तेज़ हो सकता है, और external code compile करने का बोझ भी घटाता है
- HTTP, QUIC, graphics, tokio जैसे कठिन क्षेत्रों की libraries ज़रूरी हैं, लेकिन सिर्फ़ एक function के लिए बड़ा dependency graph स्वीकार करने के फ़ैसले पर ज़्यादा शक किया जाना चाहिए
dependency बढ़ने से बनता maintenance treadmill
- डेवलपर productivity के नाम पर आसानी से packages इंस्टॉल कर लेते हैं, लेकिन नतीजे में वे updates, patches, audits और transitive dependency management से जुड़ी अंतहीन dependency churn में फँस जाते हैं
- जिन ecosystems में packaging solutions अच्छी तरह विकसित हैं, वहाँ यह समस्या और ज़्यादा उभरती है; JavaScript और Rust इस असर से विशेष रूप से प्रभावित हैं
- Rust के उदाहरण:
- नया Tokio project 28 crate लेकर आता है
- नया Rocket project बढ़कर 172 crate तक पहुँच जाता है
- MiniJinja खुद एक single dependency के रूप में मौजूद रह सकता है, लेकिन उसका CLI variant 142 dependencies लाता है
- मूल समस्या यह है कि एक छोटी सी functionality के लिए वास्तव में ज़रूरी code से कहीं ज़्यादा external code build और manage करना पड़ता है
terminal_size दिखाता है स्थिर code का विरोधाभास
terminal_size नाम के अनुरूप terminal size पता करने वाला crate है
- इस functionality के नीचे इस्तेमाल होने वाले API computing terminals के शुरुआती दौर से लगभग स्थिर रहे हैं, फिर भी OS के अनुसार यह 3~4 अतिरिक्त crate खींच लाता है
- terminal 80x25 है या 120x40, यह जानने के लिए हज़ारों दूसरे functions compile करने की स्थिति बन जाती है
- उस crate के 26 releases हो चुके हैं, लेकिन वही functionality के लिए 10 साल पहले किसी project में डाला गया self-implementation आज भी बिना update के काम करता है
- releases ज़्यादा होने की वजह functionality का बदलना नहीं, बल्कि नीचे की platform abstraction libraries का लगातार बदलते रहना है
- UNIX में
libc dependency की एक exception है
- क्योंकि Rust platform के libc constants expose नहीं करता, और वे constants standardize भी नहीं हैं
- फिर भी
libc एक आम और हल्की dependency है, इसलिए इससे बचना मुश्किल है
security और code reuse की संस्कृति dependencies को मजबूत करती है
- “big supply chain” शैली का approach function को copy-paste करने या सीधे
unsafe इस्तेमाल करने से सावधान करता है, और platform abstraction layers पर निर्भर रहने का दबाव डालता है
- dependency समस्याओं से निपटने वाले tools देने वाली कंपनियाँ भी हैं, जो security के नाम पर dependencies को बनाए रखने और उन्हें हमेशा up-to-date करने के लिए प्रेरित करती हैं
- लेकिन कई dependencies खुद security problems का बड़ा स्रोत बन सकती हैं
- code का लक्ष्य यह होना चाहिए कि वह किसी बिंदु पर स्थिरता तक पहुँचे और फिर उसे updates की ज़रूरत न पड़े
- Rust ecosystem में भले कोई dependency स्थिर रूप से काम कर रही हो, अगर उसका bug tracker कुछ निष्क्रिय दिखे तो RUSTSEC में उसे कम आंका जा सकता है
- corporate-style code review culture ने open source को भी प्रभावित किया है
- नया और चमकदार library लाने वाले engineer के डाँट खाने से ज़्यादा reward पाने की संभावना होती है
- नतीजतन Dependabot जैसे tools आए, और projects में लगातार dependency update PR आने लगे
- कंपनियों के भीतर vendoring, internal audits और org-wide upgrades ही engineering teams को लगातार व्यस्त रख सकते हैं
छोटी functionality आप खुद बना सकते हैं
- एक ज़्यादा सरल रास्ता है कि ज़रूरी code खुद लिख लिया जाए
- शुरुआती काम थोड़ा ज़्यादा हो सकता है, लेकिन code लिख लेने के बाद न नया crate चाहिए और न upstream author के edge case fix करने का इंतज़ार करना पड़ता है
- अगर code अपनी ही use case में टूटता है, तो उसे सीधे खुद ठीक किया जा सकता है; जो code काम कर रहा है उसे ज़रूरी नहीं कि maintenance treadmill पर चढ़ाया जाए
- 2025 के हिसाब से ChatGPT या Cursor आम छोटी functionality के लिए dependency-free implementation तेज़ी से बना सकते हैं
- कई छोटे functions का maintenance overhead कम होता है, और वह लगातार dependency upgrades से कम बोझिल हो सकता है
- अगर code कुछ लाइनों का ही है, तो सिर्फ़ एक काम के लिए किसी और की हज़ारों lines compile करने की ज़रूरत नहीं है
कम dependencies को ज़्यादा महत्व देना
- हर dependency बुरी नहीं होती
- complex drivers को abstract करने वाली graphics libraries
- HTTP और QUIC जैसे protocols के implementations
- tokio जैसी महत्वपूर्ण libraries जिन्हें हटाया नहीं जा सकता और हटाने का इरादा भी नहीं है
- लेकिन अगर सिर्फ़ एक function इस्तेमाल करना है और उसके लिए सैकड़ों functions compile करने पड़ें, तो इसे warning sign मानना चाहिए
- छोटे functions खुद लिखकर transitive dependency graph से बचने वाले फ़ैसले को ज़्यादा मान्यता मिलनी चाहिए
- बड़े crate graphs को लेकर ज़्यादा संदेह होना चाहिए, और ऐसे simple, stable code को सकारात्मक नज़र से देखना चाहिए जिसे कई साल तक छूने की ज़रूरत न पड़े
sha1-smol मूल रूप से sha1 नाम से SHA1 hash calculation का standard crate बन गया था, लेकिन बाद में उसे rust-crypto को नाम देना पड़ा और बड़े crypto ecosystem के अनुरूप ढलने का दबाव आया
- नया
sha1 crate इस्तेमाल करने पर 10 dependencies साथ आती हैं
- registry में नाम का महत्व और trait compatibility की माँग के कारण इससे बचना आसान नहीं था
- MiniJinja अपने README में कम dependencies पर ज़ोर देता है
$ cargo tree
minimal v0.1.0 (examples/minimal)
└── minijinja v2.6.0 (minijinja)
└── serde v1.0.144
- MiniJinja में आख़िरी dependency हटाने के लिए एक PR मौजूद है
- सही परिस्थितियों में खुद बनाना मनाया जाना चाहिए, और कम या बिना dependencies के open source libraries बनाने वाले authors को ज़्यादा पहचान मिलनी चाहिए
1 टिप्पणियां
Hacker News की राय
Rust भाषा अपने आप में अच्छी है, लेकिन Rust dependency ecosystem पसंद नहीं है। C++ के बारे में अक्सर शिकायत होती है कि उसमें dependency जोड़ना मुश्किल है, लेकिन वही बात कभी-कभी एक feature जैसी लगती है। क्योंकि वह आपको सोचने पर मजबूर करती है कि क्या इसकी सच में ज़रूरत है
C++ में मैं dependencies को नियंत्रित करता हूँ, लेकिन Rust में यह जल्दी ही 100 के पार चला जाता है और फिर मैं हार मान लेता हूँ। security के नज़रिए से देखें तो सच कहूँ तो मुझे पता ही नहीं होता कि मैं आखिर deploy क्या कर रहा हूँ
और Rust में ABI compatibility भी नहीं है और न ही shared library culture। इसलिए यह operating system package distribution model को बिगाड़ता हुआ लगता है। Linux distribution चुनते समय आप उन लोगों पर भरोसा करते हैं जो उस distribution को build करते हैं, लेकिन Rust मुझे Python के ज़्यादा करीब लगता है जहाँ कोई भी PyPI पर कुछ भी चढ़ा सकता है
लेकिन Docker/Python/Rust का क्या? मुझे बिल्कुल नहीं पता कि मेरी Docker image, PyPI package, या Rust crate किसने बनाया है
असल में हम फिर उसी दौर में लौट आए हैं जब ZIP में EXE और DLL भेजे-लिए जाते थे। बस अब हम उसे container कहते हैं, और गर्व से root privileges के साथ चलाते हैं
जैसा लेखक ने कहा, कभी-कभी dependency source code को बस साथ मिला देना ही सबसे अच्छा होता है। पहले इसे vendoring कहा जाता था, और Rails/Ruby में यह काफ़ी महत्वपूर्ण था। बाद में इसका बड़ा फ़ायदा यह होता है कि malicious package takeover का असर नहीं पड़ता, और चाहें तो upstream security patches को आप खुद merge कर सकते हैं
NIH syndrome वाला कोई project मुझे याद नहीं आता जिसका net effect सकारात्मक रहा हो। गैर-ज़रूरी dependencies भी बहुत समय बचा देती हैं
अगर optional dependency के कुछ हिस्से हर project में फिर से बनाए जाएँ, तो अतिरिक्त bugs और edge cases, transitive dependencies के कई versions, और कई platforms के support की मरम्मत के लिए समय कहाँ से आएगा, यही सोचता हूँ
जब इसके साथ PyPI, npm, Cargo जैसे ऐसे repository जुड़ जाएँ जहाँ कोई भी upload कर सकता है, curation कमज़ोर हो, और standard library छोटी हो, तो तकलीफ़ के लिए तैयार रहना चाहिए
कौन-सी dependency लानी है, यह developer तय करता है। जिन dependencies का कोई उपयोग नहीं करेगा, वे गायब हो जाएँगी, और ज़्यादातर libraries में dependencies ज़्यादा होने का कारण यही है कि बहुत से developers dependencies के ऊपर कुछ बनाना पसंद करते हैं
अगर बात build system की है, तो Cargo Crates.io के उपयोग को मजबूर नहीं करता। बस उसे आसान बनाता है। आप CMake/Vcpkg, Conan की तरह path-based dependency इस्तेमाल कर सकते हैं, या library खुद बना सकते हैं
Crates.io का उपयोग करते हुए भी अगर बदलाव पसंद नहीं हैं, तो version pinning कर सकते हैं। यह बस latest version आसानी से लेने देता है
Rust में existing software के ऊपर software बनाना आसान है। अगर existing software या उसके बदलाव की रफ़्तार पसंद नहीं, तो Cargo को दोष देने के बजाय जैसा चाहें वैसा करें
distributions अब भी Rust packages को source से build करती हैं, और crate dependencies को repository में vendor करके रखती हैं। dependencies ज़्यादा हैं और updates भी तेज़ी से आते हैं, इसलिए यह अधिक कष्टदायक है, लेकिन इसका shared libraries से अलग मामला है
यह कहना सही नहीं है कि terminal size पता करने वाला API 50 साल तक स्थिर रहा है। मेरी जानकारी में TIOCGWINSZ ioctl कभी standardized नहीं हुआ, और Unix तथा BSD के बीच इसके कई नाम भी हैं
tcgetwinsize()function 2024 में जाकर POSIX में शामिल हुआ, और इस पूरे विषय का इतिहास काफ़ी अफ़सोसनाक है https://news.ycombinator.com/item?id=42039401। यानी Windows की तरफ़ जाने से पहले ही स्थिति ऐसी थीकभी-कभी यह ठीक है, लेकिन जिन लोगों के use case अलग हैं, या जो ऐसे systems पर इसे चलाते हैं जिन्हें लेखक खुद समझता नहीं और उपयोग नहीं करता, उनके लिए यह software को बदतर बना सकता है
library की असली क़ीमत यही है कि वह उन समस्याओं को संभालती है जो ऊपर से simple दिखती हैं लेकिन वास्तव में उनमें काफ़ी complexity होती है। मुझे यह भी नहीं लगता कि Windows/Linux दोनों पर चलने वाली library के लिए 3~4 dependencies बहुत ज़्यादा हैं। अच्छे हालात में उस library का लेखक उस domain में मुझसे ज़्यादा अनुभवी होता है, और जिन अज्ञात बातों का मुझे पता नहीं, उन्हें भी संभाल सकता है
ioctlमें निश्चित ही समस्याएँ हैं, लेकिनterminal-sizeया उसकी dependencies में कई releases के दौरान हुए बदलावों काioctl/TIOCGWINSZconstant याwinsizestruct से कोई संबंध नहीं है। वह code बदला ही नहीं हैterminal-sizecrate बस Rustix केtcgetwinsizeको call करता है, और Rustix आगे libc केtcgetwinsizeको call करता है। इसलिए वही काम सीधे करने पर dependencies काफ़ी कम की जा सकती हैं, और इसकी क़ीमत बस Windows support जैसी चीज़ होगीयह API 50 साल या 25 साल से stable है या नहीं, यह एक detail भर है। वह dependency उस complexity को संभालने की कोशिश तक नहीं करती, और यह संभावना भी कम है कि वह function निकट भविष्य में बदल जाए या हटा दिया जाए
ज़्यादातर चीज़ें आप उसमें सीधे लिख सकते हैं, लेकिन terminal size या raw mode जैसी सुविधाएँ बुरी तरह अव्यवस्थित हैं और
ioctlजैसी चीज़ों की ज़रूरत पड़ती है। सच कहूँ तो यह काफ़ी ख़राब हैलेकिन libraries उससे भी बदतर हैं। अगर आप
ncursesसे link नहीं करना चाहते, तो फिर भगवान ही मालिक हैहाल ही में 2006 में बनाया गया अपना पहला startup web app फिर से चालू किया। यह media sharing-केंद्रित social media site थी, और उस समय के हिसाब से काफ़ी साधारण LAMP stack पर बनी थी। PHP 5, MySQL 3.2 था, लेकिन उस दौर के social media features ज़्यादातर इसमें मौजूद थे
नई CI/CD तकनीकों को खुद आज़माना था, इसलिए इस app को सीखने के लिए ज़रूरत से ज़्यादा engineer करते हुए deployment process बना रहा हूँ। WordPress या Hello World app भी इस्तेमाल कर सकता था, लेकिन यह कहीं ज़्यादा मज़ेदार है
PHP लगभग पूरी तरह खुद लिखा था। authentication/authorization, template, form processing आदि के लिए libraries भी खुद बनाई थीं, और email भेजने के लिए सिर्फ़ एक PEAR library इस्तेमाल की थी। frontend pure HTML था, JavaScript लगभग नहीं के बराबर था, और media playback के लिए Flash इस्तेमाल होता था। 2006 में ज़्यादातर चीज़ें ऐसे ही बनती थीं
19 साल पुराने app को फिर से चलाने में सिर्फ़ लगभग एक घंटा लगा। पुराने PHP
mysqldriver कोmysqliमें बदला, और MySQL 8 के हिसाब से schema और कुछ queries adjust कीं। ज़्यादातर काम अब reserved word बन चुके शब्दों को backtick में wrap करना और column defaults के सख़्त नियमों को ठीक करना था। जो चीज़ नहीं चली, वह सिर्फ़ Flash थादूसरी ओर, मौजूदा नौकरी में Java 8 में लिखे गए दर्जनों Spring Boot apps चला रहे हैं, और दर्जनों dependencies से निकली vulnerabilities की सूची पन्नों में जमा हो रही है। एक चीज़ update करो तो दूसरी libraries भी साथ में बढ़ानी पड़ती हैं, और transitive dependencies की वजह से यह बुरा सपना बन जाता है। इसलिए सिर्फ़ सबसे critical vulnerabilities को न्यूनतम स्तर पर संभालते हैं, और सब कुछ upgrade करने की कोई व्यावहारिक योजना नहीं है
मज़ेदार बात यह है कि 2006 का PHP app जो करता था और आज के Spring Boot apps जो करते हैं, उनमें बहुत बड़ा फ़र्क नहीं है। आख़िरकार सब कुछ CRUD ही है, बस उसके आसपास enterprise साज-सज्जा और tools बहुत ज़्यादा जुड़ गए हैं
checklist की हर item के लिए अलग dependency नहीं खींचते। कुछ duplicate work हो सकता है, लेकिन upgrade path बहुत आसान हो जाता है
scpसे deploy होने वाले पुराने LAMP applications में आम तौर पर यह समस्या नहीं होतीउदाहरण के लिए FHIR या HL7 जैसे standard communication message formats के लिए आप शायद पहले से जटिल standard definitions को पूरी तरह खुद implement नहीं करना चाहेंगे
cryptographic functions भी खुद लिखना आम तौर पर खुद को नुकसान पहुँचाने जैसा है, और वर्षों में सामने आए गंभीर security issues यह साबित कर चुके हैं
अभी का दौर इस बात पर ध्यान देने का है कि solutions सही तरीके से कैसे बनते हैं, उससे ज़्यादा business problems को कैसे solve किया जाए। AI के आने के बाद यह और भी महत्वपूर्ण हो गया है, क्योंकि बहुत सा code ऐसे लगता है जैसे आँख बंद करके जोड़ दिया गया हो
हर चीज़ खुद बनाने में समय लगाना लंबी अवधि में फ़ायदेमंद हो सकता है, लेकिन पहले competition में टिके रहना ज़रूरी है। हो सकता है competitor शुरुआत में तेज़ी से लिखे गए और बाद में फेंक देने लायक code के सहारे पहले ही market पकड़ चुका हो
हाल ही में Java 8, Spring Boot 2, Swagger से Java 17, Spring Boot 3.3, OpenAPI 3 पर गए, और यह काफ़ी painless रहा
अभी कुछ direct dependencies और transitive dependencies को और बढ़ाना बाकी है, लेकिन सबसे बड़ी रुकावट migration के साथ संभल गई
लेकिन C libraries से link न कर रहे हों, तो private transitive dependencies से कोई फ़र्क नहीं पड़ता। एक ही dependency tree में SemVer-compatible न होने वाले crate versions जितने चाहो रख सकते हो, और ज़रूरत पड़े तो कई versions पर सीधे depend भी कर सकते हो
Java-शैली के बड़े पैमाने वाले bulk upgrades की ज़रूरत नहीं पड़ती; बस जिस एक में vulnerability है, उसी को upgrade करो। मेरी जानकारी में C# में भी ऐसा कुछ मिलता-जुलता है, बस थोड़ा अधिक verbose है
100% सहमत। NodeJS ने मेरे career पर बड़ा असर डाला, लेकिन NPM ने मेरे अस्त-व्यस्त बचपन से भी बड़ा trauma दिया
कल्पना करो कि कोई नया programmer अपने दोस्तों को दिखाने के लिए नया app बना रहा है। वह नई dependency जोड़ना चाहता है, लेकिन वह दूसरी dependencies से मेल नहीं खाती, इसलिए वह सब कुछ update करने का फ़ैसला करता है। अगले ही पल कुछ भी काम नहीं करता और Babel चिल्लाने लगता है
फिर भी लगता है कि किसी तरह ठीक हो जाएगा, लेकिन जल्द ही ऐसे खुले Git issues दिखने लगते हैं जहाँ बुनियादी चीज़ें भी सचमुच काम नहीं कर रहीं। उदाहरण के लिए Expo में एक issue खुला है कि basic नया React Native project Android पर build ही नहीं होता
आधे मामलों में तो लगता है किसी को परवाह ही नहीं, और उस हालत में समाधान भी Node ecosystem में नहीं बल्कि Android ecosystem के किसी कोने में मिलता है। नीचे तक सब duct tape से जुड़ा है। फिर भी, अगर अरबों डॉलर के projects भी टूटी हुई templates ship कर सकते हैं, तो मेरे side project का आधा हिस्सा न चलने पर मुझे impostor syndrome महसूस करने की ज़रूरत नहीं—यह भरोसा तो मिलता है
मैंने पहली dependency लाने के क्षण को ही एक निजी असफलता की तरह देखना शुरू कर दिया। क्योंकि उसी पल, साधारण JS को सिर्फ़ script tag से load करने के बजाय, केवल
package.jsonइस्तेमाल करने के लिए packaging और organization की पूरी मशीनरी की ज़रूरत पड़ने लगती हैGo से Rust ecosystem में आते समय यह बात चौंकाने वाली लगी। एक mature Go project, जैसे किसी कंपनी का production web backend, transitive dependencies सहित भी 10~20 dependencies ही रख सकता है
जैसा लेख में कहा गया है, एक छोटा Rust project भी इससे कहीं ज़्यादा dependencies ले सकता है, और async काम करें तो यह लगभग तय है
यह culture की वजह से है या language features की, यह स्पष्ट नहीं है। उदाहरण के लिए Go में interfaces implicitly satisfy होते हैं, इसलिए implementation बताने के लिए कुछ import करने की ज़रूरत नहीं होती
Go में जो चीज़ें language या standard library में मिल जाती हैं, उनके लिए Rust में dependencies चाहिए होती हैं: green threads, channels, regex, HTTP client, HTTP server, time, command-line flags, logger, animated GIF read/write आदि
मुझे Go भाषा खुद बहुत पसंद नहीं, लेकिन tools और standard library के मामले में यह आगे है, और जो मैंने इस्तेमाल किए हैं उनमें निश्चित रूप से सबसे बेहतर है
मैं इस तर्क से काफी हद तक सहमत हूँ। अपनी library के बाहर दूसरी libraries की abstractions को leak होने देने की hidden cost बहुत होती है। इसका मतलब यह नहीं कि ऐसा कभी नहीं करना चाहिए, लेकिन फैसला लेते समय future cost को संतुलित नज़र से देखना चाहिए
अगर wrapping package बार-बार design बदलता है या अपने goals बदलता है, तो instability पैदा होती है। जो पहले software engineering में guarantee का सबसे सरल रूप था, वही functionality गायब हो जाती है या टुकड़ों में बँट जाती है
और मैं career open source contributor तो नहीं हूँ, लेकिन किसी खास domain को अच्छी तरह जानने वाले owners के लिए ecosystem में हिस्सा लेना मुश्किल हो जाता है। अगर “मैंने X को साफ-सुथरे तरीके से करने का तरीका बनाया” यह बनकर “लोगों को Z भी पसंद आ सकता है, इसलिए X को Y के नीचे fit होना चाहिए” जैसी कई हफ्तों की बहस, negotiation और politics में बदल जाए, तो यह सबका समय बर्बाद करता है
ज़िंदगी में मैंने एक बात सीखी है कि सबसे simple चीज़ें सबसे लंबे समय तक टिकती हैं। minilith और monolith की ज़्यादा तारीफ़ होनी चाहिए। यह सिर्फ Rust की समस्या नहीं है; मैंने इसे कई languages में देखा है। open source communities अक्सर atomic-size packages को बहुत push करती हैं, और अक्सर सोचता हूँ कि क्या इसका मकसद actual user community के हित से ज़्यादा flag planting या ownership transfer तो नहीं है
2025 में मैं कुछ हद तक संयोग से इस नज़रिए पर पहुँचा कि आम functions के dependency-free implementations ChatGPT या Cursor से बनवा लेना ज़्यादा तेज़ है, और अब मैं इससे अधिक सहमत होता जा रहा हूँ। खासकर बड़े React apps के dependency hell से गुजरने के बाद
SaaS या third-party service dependencies के बारे में भी सोचना चाहिए। उनमें से बहुत-सी common patterns हैं, यानी पहले से solved problems, जिन्हें LLM तेज़ी से replicate कर सकता है
सीमित scope वाली समस्याओं में यह काफ़ी असरदार है, और मैं AI से ऐसा implementation बनवा सकता हूँ जो मेरे खुद लिखे हुए से अधिक complete और robust हो
बाद में अगर library जोड़ने का फैसला भी हो, तो natural encapsulation पहले से बन जाती है। बस मेरी function implementation को library इस्तेमाल करने के लिए बदलना होगा; जहाँ-जहाँ उसका उपयोग हो रहा है वहाँ सब जगह हाथ लगाने की ज़रूरत नहीं पड़ेगी। समय आने पर कई libraries को आज़माना भी आसान हो जाता है
विडंबना यह है कि यह काफ़ी दिलचस्प है। Armin, Python web framework Flask के original author हैं। लगभग उसी समय Bottle नाम की एक बहुत मिलती-जुलती library भी थी
features लगभग समान थे, लेकिन Flask बहुत लोकप्रिय हो गया, जबकि मैंने हमेशा Bottle को पसंद किया। क्योंकि वह single-file थी और उसकी कोई dependency नहीं थी, इसलिए उसे project में बस copy कर देना बहुत आसान था
उसमें बदलाव करना भी आसान था, और आखिरकार वह इतनी छोटी थी कि पूरी की पूरी समझ में आ जाती थी। server और websocket के लिए मैं Gevent जोड़ता था, लेकिन उस तरीके से भी काफी heavy projects बनाए जा सकते थे
आज भी छोटे web projects के लिए Bottle इस्तेमाल करने का मन मज़बूती से होता है। बस अफ़सोस यह है कि Python ने पिछले कुछ वर्षों में जो modern practices अपनाई हैं, उनका साथ वह ज़्यादा नहीं दे पाई, इसलिए अब वह थोड़ी पुरानी लगती है
खुद बनाना हो तो सक्षम engineering skill चाहिए। अगर आपके पास केवल ऐसे engineers हों जो हमेशा NPM या PyPI जैसे ecosystem से libraries उठाते आए हों, तो कई समस्याओं के समाधान खुद विकसित करने में उन्हें कठिनाई होगी। खासकर तब, जब वह समाधान लंबे समय तक टिके और ज़रूरी flexibility भी रखता हो
“खुद को dead end में program न कर लेना” इसके लिए बहुत अभ्यास चाहिए
कई बार यह भी देखा है कि मौजूदा library से बेहतर काम आसानी से किया जा सकता है। एक project में मैंने Markdown के एक variant के लिए parser बनाया था, जिसमें file के top पर metadata होता था। grammar छोटी थी, और parser एक screen से भी कम code में बन गया
लेकिन उसी file को parse करने वाली frontend library उम्मीद से कहीं ज़्यादा खराब निकली। metadata identifiers में hyphen हो तो वह टूट जाती थी, और पता चला कि वह metadata identifiers को सीधे object member names की तरह इस्तेमाल कर रही थी। यानी साधारण JSON object इस्तेमाल करने के बजाय उसने names को कृत्रिम रूप से सीमित कर दिया था, और
something-somethingजैसे hyphen वाले नामों पर टूट जाती थीआखिरकार मेरा parser फेंक दिया गया, क्योंकि लोगों का कहना था कि उसे “maintain” करना पड़ेगा। लेकिन वह बस काम करता था, और grammar changes के अनुसार आसानी से ढल भी सकता था। parser के बारे में थोड़ा-सा भी जानने वाला उसे आसानी से समझ सकता था
यक़ीन करना मुश्किल है, लेकिन लगता है कि team में parser generator library से parser लिखकर देखने वाला मेरे अलावा कोई नहीं था। ऐसे उदाहरण और भी बहुत हैं
अगर आप सिर्फ़ एक फ़ंक्शन इस्तेमाल कर रहे हैं लेकिन उसके लिए सैकड़ों चीज़ें compile करनी पड़ रही हैं, तो चेतावनी की घंटी बजनी चाहिए। लगभग एक साल पहले मैंने third-party dependencies को update करने वाला एक प्रोजेक्ट किया था
उनमें से एक बहुत समृद्ध math library थी जिसमें तरह-तरह के mathematical functions थे। थोड़ा गहराई से देखने पर पता चला कि हम उसमें से सिर्फ़ list का median निकालने वाली एक ही method इस्तेमाल कर रहे थे
मैंने ज़िम्मेदार engineer को Wikipedia पेज दिखाया और कहा कि dependency हटा दें और उस math operation के लिए एक single method खुद लिखें
लेकिन असली समस्या third-party dependency का इस्तेमाल करना नहीं है; समस्या यह है कि library का सिर्फ़ एक छोटा हिस्सा लेने की सुविधा चाहिए। अगर किसी विशाल library का सिर्फ़ छोटा-सा भाग चाहिए, तो पूरी चीज़ क्यों लानी पड़े? इसके लिए “microframework” का सुझाव दिया गया था, ऐसी बात मैंने सुनी है
दुर्भाग्य से ऐसा नहीं लगता कि लोग dependency के default feature set को ध्यान से देखते हैं और उसे सक्रिय रूप से कम करते हैं। अतिरिक्त features लाने का syntax तो आसान है, लेकिन default में enabled optional features हटाने के लिए
no-default-featuresदेना पड़ता है, और फिर जो default features वास्तव में चाहिए उन्हें एक-एक करके दोबारा जोड़ना पड़ता है, इसलिए यह ज़्यादा झंझट वाला हैइससे भी बुरा यह है कि अगर कोई library अपनी dependencies से अनावश्यक features हटाने की सुविधा देना चाहती है, तो उसे अपने खुद के features बनाकर उन्हें हर dependency के features से map करना पड़ता है। उदाहरण के लिए, अगर 5 dependencies हैं और हर एक में 1 required dependency और 4 optional dependencies हैं, तो users को transitive features पर पूरी तरह control देने के लिए अपनी library में 20 mapping features बनाने पड़ेंगे। इसमें वे features शामिल भी नहीं हैं जो अपने code के downstream users की सुविधा के लिए अलग से बनाने होंगे
मुझे लगने लगा है कि features के इर्द-गिर्द usability, खासकर अनावश्यक bloating कम करने के मामले में, इतनी खराब है कि वही Rust compile time समस्याओं का catalyst बनती है। ऐसा लगता नहीं कि इस विषय पर बहुत व्यापक चर्चा होती है, इसलिए शायद मुझे इस पर अपनी ठोस राय एक blog post में लिखनी पड़े, ताकि मौजूदा स्थिति बनी रहे तो आगे उसे दिखाया जा सके
अगर trust system अच्छा हो, तो एक मायने में मुझे नहीं लगता कि यह समस्या है
Shadcn जैसा तरीका, यानी कोई component पसंद आए तो उसे अपनी library में copy कर लेना, यह विचार भी मुझे कुछ हद तक पसंद है। लेकिन अगर vulnerability आ जाए, तो मुझे पता ही नहीं चलेगा कि मैं प्रभावित हूँ या नहीं
कुछ code में यह ठीक है, लेकिन कुछ और code में आपको सचमुच इस बात पर निर्भर रहना पड़ता है कि उसे जितनी ज़्यादा नज़रें देखें उतना बेहतर
Median(list) { let len = length(list) if len % 2 == 0 { let x = floor(len/2) return (list[x] + list[x+1]) / 2 } return list[len/2] }बशर्ते list पहले से sorted हो। यहाँ
is_oddको call करने का लालच मैंने रोका