DanLevy.net

خبير خطوط الأنابيب: تمرير الحالة

أهلاً بالإغلاق، صديقي القديم.

Hero image for خبير خطوط الأنابيب: تمرير الحالة

سيد خطوط الأنابيب: تمرير الحالة

هل واجهت تحديات في تمرير الحالة باستخدام خطوط الأنابيب الوظيفية؟

تنظيم الكود (أو عدمه) يؤثر بشكل مباشر على سهولة تمرير الحالة.

في هذه المقالة، سنستكشف تقنية فعالة لتمرير الحالة عبر خط أنابيب. وعلى طول الطريق، سنحسن تنظيم الكود وقراءته.

المقتطف “الحقيقي” التالي سيكون محور هذه المقالة: دالة checkout، التي تقبل userId ومصفوفة من products. تُرجع سلسلة Promise تنفذ 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 هو الذي يأتي أولاً؟)

قبل أن نقرر كيفية تحسين هذا الكود، دعنا نحدد بعض الإيجابيات/السلبيات.

الإيجابيات والسلبيات

الإيجابيات

السلبيات

الحل، الجزء 1: إنشاء وحدة!

تتعلق هذه التقنية بتنظيم الدوال ذات الصلة في وحدة واحدة (مثل CartHelpers). لا تتطلب نمطًا محددًا. استكشف دوال المصنع، الفئات، الإغلاقات، Mixins، إلخ. ابحث عن ما يناسب مشروعك وفريقك.

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);
}

بعض الفوائد المباشرة:

من خلال تجميع الدوال ذات الصلة، نخلق فرصة لتقليل المساحة السطحية المكشوفة (مثل دوال 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);
// 🌈 Functions stack like Lego & read like normal "Human Words!" 💅
return Promise.resolve(products)
.then(cart.getProductsSubtotal)
.then(cart.applyTaxes)
.then(cart.purchaseProducts)
.then(cart.sendReceipt);
};

إذا شعرت أن دمج المعاملات في وسيط واحد (كائن) غير طبيعي، فكر في تقسيم دوالك أو دمجها في وحدات ذات نطاق أكثر ملاءمة.

من أين نبدأ؟

ابحث عن الدوال ذات الصلة، واجمعها معًا. (مثل CartHelpers.)

جزء من التحدي عند إيجاد الوحدات المنطقية المحتملة هو تحديد الكود ذي الصلة في المقام الأول.

ما الذي يجعل الدوال ذات صلة؟

خدعة أنيقة: ابحث عن التكرار في معاملات الدوال. اسأل: هل هناك علاقة قائمة؟ أم مسؤولية كامنة؟

بينما لا توجد “إجابة صحيحة” واحدة لتصميم الوحدات، من المفيد تحديد 2-3 خيارات للتنظيم - ارسم مخططًا، اكتب كودًا “تخيليًا”، واسأل “هل يثير البهجة؟”

قد تشعر أن cart.sendReceipt() لا ينتمي إلى دوال الدفع. ربما customerNotifications.sendReceipt() هو مكان أفضل لرسائل العملاء. إذا كانت CartHelper ذات أهمية كافية، فقد تعمل كـ متحكم يستدعي داخليًا جميع الخدمات اللازمة، مثل customerNotifications.

كيف تعرف أنك تساعد؟

إذا لم تتأثر قابلية القراءة عند التخلص من الوسائط المؤقتة، تهانينا!!! فمن المحتمل أنك بنيت وحدة ذات نطاق واضح ومتين!

إذن، هذا يطرح السؤال: أين نضيف الوظائف؟

في تجربتي، هناك استراتيجيتان رئيسيتان لتقييم إضافة الوظائف:

  1. تمديد/إعادة هيكلة الطريقة الحالية. (عندما يكون الكود الجديد قريبًا بدرجة كافية من الكود الحالي.)
  2. إنشاء دالة جديدة (خامسة) في المكان المطلوب في السلسلة. (بافتراض أن الكود الجديد غير مرتبط بالدوال الحالية.)

في النهاية، هذا يسهل تحديد مكان الوظائف الجديدة. (مثل cart.applyDiscounts()، cart.applyTaxes()، rewards.getBalance().)

الخاتمة

قد يكون تمرير الحالة عبر خط أنابيب معقد أمرًا صعبًا. ومع ذلك، مع القليل من ممارسة إعادة الهيكلة، ستجد نفسك تكتب كودًا أكثر قابلية للقراءة، مع عبء معرفي أقل.

أسئلة؟ تعليقات؟ مخاوف؟ لا تتردد في التواصل @justsml أو البريد الإلكتروني.

ترقبوا الجزء التالي من السلسلة

سوف نستكشف كيفية إخراج الحالة إلى الخارج، وتوسيع الوظائف في الوحدة الخاصة بنا!

قراءات ذات صلة