إعادة هيكلة الكود باستخدام التعميم بالمعاملات (Parameterization)
تقليل التعقيد عبر إعادة هيكلة الكود بأسلوب التعميم بالمعاملات (parameterization).

تحديث (13 يوليو 2017): أصلحت منطق حساب المتوسط المرجّح الخاطئ في الكود، وأضفت مثالًا عمليًا يمكنكم تشغيله على أجهزتكم. :awesome:
صادفت في مناسبات عديدة أجزاء من الكود "شبه متطابقة" منتشرة في أنحاء قاعدة الكود. والمشكلة في هذا النوع من الكود الرديء (code smell) أنكم حين تريدون تغيير تفصيل صغير في دالة بعينها، تضطرون إلى البحث عن كل المواضع التي كتبتموها فيها وتعديلها جميعًا، وهذه ليست فكرة جيدة.
الكود المكرر سبب رئيسي في صعوبة صيانة السكربتات. fancy-quote
_Brian Marick في كتاب Everyday Scripting with Ruby _ الأسلوب الذي أستخدمه كثيرًا وأجده سهلًا للغاية هو التعميم بالمعاملات (parameterization). سأشرح اليوم كيف يعمل من خلال مثال صغير ولطيف.
حساب المتوسط المرجّح
لدي تقارير أحتاج فيها إلى حساب المتوسط المرجّح لحقل إجمالي الربح، على أن يكون الترجيح بحسب إيرادات المبيعات. وهذا ما توصلت إليه (بلغة Ruby).
def weight_avg_gross_profit(reports)
# find total weight, sum of sale_revenue
total_weight = reports.reduce(0) { |sum, r| sum + r.sale_revenue }
# find sum products
sum_prod = reports.reduce do |sum, r|
sum + (r.percent_gross_profit * r.sale_revenue)
end
# simple weight average formula, (w1*x1 + w2*x2) / (w1 + w2)
sum_prod / total_weight
end
lang-ruby
أمر بسيط، أليس كذلك؟ نحتاج الآن إلى دالة أخرى تفعل الشيء نفسه تمامًا، لكن لحقل آخر اسمه cogs. وأسهل ما يمكن فعله هنا هو نسخ الدالة وتغيير ما يلزم تغييره.
def weight_avg_gross_profit(reports)
# find total weight, sum of sale_revenue
total_weight = reports.reduce(0) { |sum, r| sum + r.sale_revenue }
# find sum products
sum_prod = reports.reduce(0) do |sum, r|
sum + (r.percent_gross_profit * r.sale_revenue)
end
# simple weight average formula, (w1*x1 + w2*x2) / (w1 + w2)
sum_prod / total_weight
end
def weight_avg_gross_profit(reports)
# find total weight, sum of sale_revenue
total_weight = reports.reduce(0) { |sum, r| sum + r.sale_revenue }
# find sum products
sum_prod = reports.reduce(0) do |sum, r|
sum + (r.cogs * r.sale_revenue)
end
# simple weight average formula, (w1*x1 + w2*x2) / (w1 + w2)
sum_prod / total_weight
end
lang-ruby
اجعله يعمل أولًا، ثم اجعله صحيحًا ❤ fancy-quote
حصلنا على ما نريد، أليس كذلك؟ رائع، لكن ماذا لو احتجنا إلى المتوسط المرجّح لحقول أخرى؟ وماذا لو احتجنا إلى تغيير الترجيح من sale_revenue إلى شيء آخر؟ إنشاء دالة جديدة تتشارك هذا المنطق سيعيدنا إلى المشكلة التي تحدثت عنها سابقًا، أي مشكلة التكرار.
التعميم بالمعاملات عمليًا
ماذا نلاحظ الآن؟ هاتان الدالتان متطابقتان تقريبًا، أليس كذلك؟ الفرق الوحيد هو الـ block الذي مررته إلى دالة map.
لنستخرج الكود المتشابه ونضعه في دالة جديدة سأسميها w_avg، ثم نحوّل الاختلافات إلى معاملات.
ملاحظة: تستطيع Ruby استدعاء دالة ديناميكيًا عبر تمرير string أو symbol إلى الدالة send
حذفت الدالة المكررة، وأصبح برنامجنا يبدو هكذا.
def weight_avg_gross_profit(reports)
weight_avg(reports, 'gross_profit')
end
def weight_avg_cogs(reports)
weight_avg(reports, 'cogs')
end
def weight_avg(reports, value)
total_weight = reports.reduce(0) { |sum, r| sum + r.sale_revenue }
sum_prod = reports.reduce(0) do |sum, res|
sum + (res.send(value) * res.sale_revenue)
end
sum_prod / total_weight
end
lang-ruby
أصبح منطق الدالتين الآن في مكان واحد. ويمكنني بسهولة توسيعه لحساب weight_avg لأي قيمة من التقرير بدالة من سطر واحد.
إليكم رابطًا لنسخة عاملة من برنامج Ruby هذا
_ نزّلوه وشغّلوه _
ruby parameterized_working_example.rb
lang-bash
خطوة أبعد
التعميم بالمعاملات حيلة صغيرة لطيفة، لكنه ليس حلًا سحريًا. ففي بعض الأحيان يكون من الحكمة الإبقاء على قدر بسيط من التكرار بدلًا من دالة مفرطة في التجريد لا يمكن توسيعها أو تعديلها. وكما قالت Sandi Metz في RailsConference 2014
التكرار أقل تكلفة بكثير من التجريد الخاطئ. fancy-quote
ملاحظة أخيرة
أؤمن بأن علينا ألا نركّز على جعل الأشياء تعمل فحسب، بل على بناء قاعدة كود نظيفة يسهل على أي مطوّر يواصل العمل على المشروع صيانتها.


