पाइपलाइन के मास्टर: स्टेट पास करना
नमस्ते क्लोजर, मेरे पुराने मित्र।
पाइपलाइन के मास्टर: स्टेट पास करना
क्या आपने फंक्शनल पाइपलाइन का उपयोग करते हुए स्टेट पास करने में चुनौतियों का सामना किया है?
आपके कोड का संगठन (या उसकी कमी) सीधे तौर पर इस बात को प्रभावित करता है कि स्टेट कितनी आसानी से पास की जा सकती है।
इस लेख में हम पाइपलाइन के माध्यम से स्टेट पास करने की एक प्रभावी तकनीक का पता लगाएंगे। इस प्रक्रिया में हम अपने कोड के संगठन और पठनीयता में सुधार करेंगे।
निम्नलिखित “वास्तविक” स्निपेट इस लेख के लिए हमारा फोकस होगा: एक चेकआउट फंक्शन, जो userId और products की एक array स्वीकार करता है। यह एक Promise-chain लौटाता है जो क्रम में 4 फंक्शन्स निष्पादित करता है।
const checkout = (userId: number, products: number[]) => { return getProductsSubtotal(userId, products) .then(subTotal => applyTaxes(userId, subTotal)) .then(total => purchaseProducts(userId, total)) .then(result => sendReceipt(userId, result));};रुको एक सेकंड, यह कोड वास्तव में काफी अच्छा है, जहाँ तक JS में पाइपलाइन्स का सवाल है!
यह कुछ सूक्ष्म समस्याओं से ग्रस्त है जो बड़ी समस्याओं में मिल सकती हैं।
एक समस्या यह है कि हम userId को प्रत्येक (तार्किक रूप से संबंधित) फंक्शन में बार-बार पास कर रहे हैं। अब इसे एक और समस्या के साथ जोड़ें जिसे डेवलपर्स और TypeScript दोनों आसानी से चूक जाते हैं: न्यूमेरिक आर्गुमेंट्स को पलटने से आसानी से एक साइलेंट बग बन जाता है। (applyTaxes और purchaseProducts देखें। क्या userId पहले जाता है या amount?)
इससे पहले कि हम तय करें कि इस कोड को कैसे बेहतर बनाया जाए, आइए कुछ पक्ष/विपक्ष की पहचान करें।
पक्ष और विपक्ष
पक्ष
- क्लोजर का अच्छा उपयोग!
userIdऔरproductsको एक बार पास करना! - आर्गुमेंट नामकरण में स्थिरता।
- चेकआउट के लिए 4 प्रमुख फंक्शन्स की अपेक्षाकृत प्रभावी और संक्षिप्त रचना।
- “मुफ्त” एरर फ्लो कंट्रोल। (एरर्स किसी भी नेस्टेड फंक्शन से बबल अप होते हैं,
checkout()द्वारा लौटाए गए Promise को रिजेक्ट करते हुए।)
विपक्ष
userIdको बार-बार पास करना थकाऊ है।- फंक्शन्स सिंगल-पैरामीटर (यानी unary) नहीं हैं। यह कंपोजेबिलिटी को प्रभावित करता है। अंतिम उदाहरण देखें कि क्यों?
- यह स्पष्ट नहीं हो सकता कि प्रत्येक फंक्शन क्या लौटाता है। (क्या यह ईमेल भेजने का परिणाम है, या वह
resultवेरिएबल? या?) - फंक्शनैलिटी कैसे जोड़ें यह स्पष्ट नहीं है। (उदाहरण के लिए, मान लीजिए हमें कस्टमर डिस्काउंट/क्रेडिट/पॉइंट्स आदि लोड करने की आवश्यकता है।)
- कभी-कभी “अस्थायी” पैरामीटर नाम (जैसे प्रत्येक
.then(param => {})में) संदर्भ जोड़ते हैं। हालाँकि समय के साथ, वे नामकरण की गंदगी के घर बन जाएंगे।
समाधान, भाग 1: एक मॉड्यूल बनाएं!
यह तकनीक संबंधित फंक्शन्स को एक ही मॉड्यूल (उदाहरण CartHelpers) में व्यवस्थित करने के बारे में है। यह किसी विशिष्ट पैटर्न की मांग नहीं करता। फैक्टरी फंक्शन्स, क्लासेस, क्लोजर्स, मिक्सिन्स आदि का पता लगाएं। अपने प्रोजेक्ट और टीम के लिए जो समझ में आए उसे खोजें।
CartHelpers फैक्टरी
CartHelpers मॉड्यूल का उदाहरण, जहाँ userId को एक बार पास किया जाता है, और सभी मेथड्स सिंगल-आर्गुमेंट हैं।
const CartHelpers = (userId: number) => { return { getProductsSubtotal: products => getProductsSubtotal(userId, products), applyTaxes: subTotal => applyTaxes(userId, subTotal), purchaseProducts: total => purchaseProducts(userId, total), sendReceipt: invoice => sendReceipt(userId, invoice) };};CartHelpers क्लास
यदि क्लासेस आपकी चीज़ हैं, तो अनुकूलित करना आसान है:
class CartHelpers { constructor(userId) { this.userId = userId; } getProductsSubtotal = products => getProductsSubtotal(this.userId, products); applyTaxes = subTotal => applyTaxes(this.userId, subTotal); purchaseProducts = total => purchaseProducts(this.userId, total); sendReceipt = invoice => sendReceipt(this.userId, invoice);}कुछ तत्काल लाभ:
- दोहराव वाले वेरिएबल पास करने को समाप्त करें।
- DRY:
CartHelpersदोहराए गए आर्गुमेंटuserIdको абстракт करता है। - प्रत्येक मेथड केवल आवश्यक आर्गुमेंट्स को स्वीकार करती है।
cart.applyTaxes(subTotal)को पढ़ना पूरी तरह से स्पष्ट है।
- DRY:
CartHelpersमें सिंगल-आर्गुमेंट फंक्शन्स अधिक पठनीय हैं, स्पष्ट उद्देश्य के साथ।
संबंधित फंक्शन्स को समूहीकृत करके, हम एक्सपोज्ड सरफेस एरिया को कम करने का अवसर बनाते हैं (उदाहरण checkout(), CartHelpers की ‘पब्लिक’ मेथड्स।)
कम सरफेस एरिया === कम कॉग्निटिव लोड, बेहतर टेस्टिंग और मेंटेनेबिलिटी। डिज़ाइन सिस्टम को इरादे और फोकस के साथ बनाएं। ✨
Checkout और CartHelpers का उपयोग
आइए देखें कि checkout() फंक्शन अब कैसा दिखता है:
export const checkout = ({ userId, products }) => { const cart = CartHelpers(userId);
return Promise.resolve(products) .then(products => cart.getProductsSubtotal(products)) .then(subTotal => cart.applyTaxes(subTotal)) .then(total => cart.purchaseProducts(total)) .then(result => cart.sendReceipt(result));};आगे के सुधारों के साथ Checkout
क्या इसे और बेहतर बनाया जा सकता है? हाँ! हमें बिल्कुल भी आर्गुमेंट्स को दोहराने की आवश्यकता नहीं है!
जब किसी फंक्शन के आर्गुमेंट्स पिछले फंक्शन्स के आउटपुट द्वारा प्रदान किए जाते हैं, तो आप कोड को और भी सरल बना सकते हैं।
export const checkout = ({ userId, products }) => { const cart = CartHelpers(userId);
// 🌈 फंक्शन्स लेगो की तरह स्टैक होते हैं और सामान्य "मानव शब्दों" की तरह पढ़े जाते हैं! 💅 return Promise.resolve(products) .then(cart.getProductsSubtotal) .then(cart.applyTaxes) .then(cart.purchaseProducts) .then(cart.sendReceipt);};यदि पैरामीटर्स को सिंगल (ऑब्जेक्ट) आर्गुमेंट्स में मिलाना अप्राकृतिक लगता है, तो अपने फंक्शन्स को तोड़ने या उन्हें अधिक उचित रूप से स्कोप्ड मॉड्यूल्स में मिलाने पर विचार करें।
कहाँ से शुरू करें?
संबंधित फंक्शन्स खोजें, और उन्हें एक साथ समूहित करें। (उदाहरण CartHelpers।)
संभावित लॉजिकल मॉड्यूल्स खोजने की चुनौती का एक हिस्सा सबसे पहले संबंधित कोड की पहचान करना है।
फंक्शन्स को संबंधित क्या बनाता है?
एक बढ़िया तरकीब: फंक्शन पैरामीटर्स में दोहराव खोजें। पूछें कि क्या कोई संबंध खेल में है? या कोई अंतर्निहित जिम्मेदारी?
- ✅ फंक्शन्स जिनमें दोहराए गए, कॉमन आर्गुमेंट्स हैं। (उदाहरण यदि 4 मेथड्स
userRewardsस्वीकार करती हैं, तो chances are आपकोRewardsया अन्य मॉड्यूल की आवश्यकता है।) - ✅ फंक्शन्स जिनके आर्गुमेंट्स सीधे पिछले फंक्शन्स के आउटपुट द्वारा प्रदान किए जाते हैं। (चरणों के अनुक्रम। उदाहरण
Extract,Transform,Load।) - ❌ फीचर एरिया से किसी भी तरह से संबंधित कुछ, “प्रोडक्ट पर्चेजिंग?”
- ❌ फंक्शन्स जिनमें कॉमन प्रीफिक्स या सफिक्स नामकरण है?
- ❌ फंक्शन्स जिन्हें बड़े ऑब्जेक्ट्स को आर्गुमेंट्स के रूप में आवश्यकता है, भले ही वे उन ऑब्जेक्ट्स के अंदर से केवल कुछ मानों का उपयोग करते हों। (उदाहरण
applyTaxes({ user, business, rewards, kitchenSink })vsapplyTaxes({ subTotal }))
हालाँकि मॉड्यूल्स को डिज़ाइन करने का कोई एक “सही उत्तर” नहीं है, संगठन के लिए 2-3 विकल्पों की पहचान करने में मदद मिलती है - एक आउटलाइन खींचें, “काल्पनिक” कोड लिखें, पूछें “क्या यह खुशी लाता है?”
आपको लग सकता है कि
cart.sendReceipt()पेमेंट-संबंधित मेथड्स के साथ नहीं belongs। शायदcustomerNotifications.sendReceipt()कस्टमर मैसेजिंग के लिए एक बेहतर घर है। यदिCartHelperमहत्व में पर्याप्त ऊँचा है, तो यह आंतरिक रूप से सभी आवश्यक सर्विसेज, जैसेcustomerNotificationsको कॉल करने वाले कंट्रोलर के रूप में कार्य कर सकता है।
आप कैसे जानते हैं कि आप मदद कर रहे हैं?
यदि ad-hoc आर्गुमेंट्स को समाप्त करते हुए पठनीयता को नुकसान नहीं होता है, बधाई हो!!! आपने संभवतः एक स्पष्ट और टिकाऊ स्कोप वाले मॉड्यूल का निर्माण किया है!
- मध्यवर्ती आर्गुमेंट्स को हटाने से ‘लेयर्स’ को उभरने के लिए मजबूर करने का तरीका होता है।
- गलत जगह पर ad-hoc कोड डंप करना कठिन होना चाहिए!
तो, यह सवाल खड़ा होता है, हम फंक्शनैलिटी कहाँ जोड़ते हैं?
मेरे अनुभव में फंक्शनैलिटी जोड़ते समय मूल्यांकन करने के लिए 2 प्राथमिक रणनीतियाँ हैं:
- मौजूदा मेथड को एक्सटेंड/रिफैक्टर करें। (जब नया कोड मौजूदा कोड के काफी करीब हो।)
- चेन में वांछित स्थान पर एक नया (5वाँ) फंक्शन बनाएं। (यह मानते हुए कि नया कोड मौजूदा फंक्शन्स से असंबंधित है।)
अंततः यह तय करना आसान हो जाता है कि नई फंक्शनैलिटी कहाँ belongs। (उदाहरण cart.applyDiscounts(), cart.applyTaxes(), rewards.getBalance()।)
निष्कर्ष
किसी जटिल पाइपलाइन के माध्यम से स्टेट पास करना मुश्किल हो सकता है। हालाँकि, थोड़े रिफैक्टर अभ्यास के साथ, आप अपने आप को अधिक पठनीय कोड लिखते हुए पाएंगे, कम कॉग्निटिव लोड के साथ।
प्रश्न? टिप्पणियाँ? चिंताएँ? बेझिझक संपर्क करें @justsml या email।
श्रृंखला में अगले भाग के लिए बने रहें
हम अपने मॉड्यूल में स्टेट को बाह्य करने और फंक्शनैलिटी को बढ़ाने का पता लगाएंगे!