Computer >> कंप्यूटर ट्यूटोरियल >  >> प्रोग्रामिंग >> Ruby

स्प्राउट क्लासेस के साथ लिगेसी Ruby को रीफैक्टर करें

कुछ लिगेसी एप्लिकेशन के साथ काम करने की सबसे मुश्किल चुनौतियों में से एक यह है कि कोड टेस्टेबल होने के लिए नहीं लिखा गया था। इसलिए **सार्थक टेस्ट लिखना मुश्किल या असंभव है**।

यह एक चिकन-एंड-एग समस्या है: लिगेसी एप्लिकेशन के लिए टेस्ट लिखने के लिए, आपको कोड बदलना होगा, लेकिन आप पहले टेस्ट लिखे बिना कोड को आत्मविश्वास से नहीं बदल सकते!

आप इस विरोधाभास से कैसे निपटते हैं?

यह माइकल फेदर्स की उत्कृष्ट *Working Effectively with Legacy Code* में कवर किए गए कई विषयों में से एक है। आज मैं किताब से एक विशेष तकनीक पर ज़ूम इन करने जा रहा हूँ जिसे **स्प्राउट क्लास** कहा जाता है।

कुछ LEGACY CODE के लिए तैयार हो जाइए!

आइए `Appointment` नामक इस पुराने ActiveRecord क्लास को देखें। यह काफी लंबा है, और वास्तविक जीवन में यह 100 की लंबाई का है।

class Appointment < ActiveRecord::Base has_many :appointment_services, :dependent => :destroy has_many :services, :through => :appointment_services has_many :appointment_products, :dependent => :destroy has_many :products, :through => :appointment_products has_many :payments, :dependent => :destroy has_many :transaction_items belongs_to :client belongs_to :stylist belongs_to :time_block_type def record_transactions transaction_items.destroy_all if paid_for? save_service_transaction_items save_product_transaction_items save_tip_transaction_item end end def save_service_transaction_items appointment_services.reload.each { |s| s.save_transaction_item(self.id) } end def save_product_transaction_items appointment_products.reload.each { |p| p.save_transaction_item(self.id) } end def save_tip_transaction_item TransactionItem.create!( :appointment_id => self.id, :stylist_id => self.stylist_id, :label => "Tip", :price => self.tip, :transaction_item_type_id => TransactionItemType.find_or_create_by_code("TIP").id ) end end 

कुछ फीचर्स जोड़ना

अगर हमें ट्रांजैक्शन-रिपोर्टिंग क्षेत्र में कुछ नई कार्यक्षमता जोड़ने के लिए कहा जाता है, लेकिन `Appointment` क्लास में बहुत अधिक निर्भरताएं हैं ताकि बहुत सारे रीफैक्टरिंग के बिना टेस्टेबल हो, तो हम कैसे आगे बढ़ते हैं?

एक विकल्प है बस परिवर्तन करना:

def record_transactions transaction_items.destroy_all if paid_for? save_service_transaction_items save_product_transaction_items save_tip_transaction_item send_thank_you_email_to_client # नया कोड end end def send_thank_you_email_to_client ThankYouMailer.thank_you_email(self).deliver end 

यह बेकार है

उपरोक्त कोड के साथ दो समस्याएं हैं:

  1. `Appointment` की कई अलग-अलग जिम्मेदारियां हैं (सिंगल रिस्पॉन्सिबिलिटी सिद्धांत का उल्लंघन), इन जिम्मेदारियों में से एक है *ट्रांजैक्शन रिकॉर्ड करना*। `Appointment` क्लास में अधिक ट्रांजैक्शन-संबंधित कोड जोड़कर, **हम कोड को थोड़ा और खराब बना रहे हैं**।

  2. हम शायद एक नया इंटीग्रेशन टेस्ट लिख सकते हैं और जांच सकते हैं कि ईमेल पार हो गया, लेकिन चूंकि हमारे पास `Appointment` क्लास टेस्टेबल स्थिति में नहीं होगी, हम कोई यूनिट टेस्ट नहीं जोड़ पाएंगे। **हम अधिक अनटेस्टेड कोड जोड़ रहे होंगे**, जो निश्चित रूप से बुरा है। (माइकल फेदर्स, वास्तव में, *लिगेसी कोड* को "बिना टेस्ट वाले कोड" के रूप में परिभाषित करते हैं, इसलिए हम लिगेसी कोड में _अधिक_ लिगेसी कोड जोड़ रहे होंगे।)

इसे टुकड़ों में बांटना बेहतर है

नए कोड को केवल इनलाइन जोड़ने की तुलना में एक बेहतर समाधान ट्रांजैक्शन-रिकॉर्डिंग व्यवहार को अपनी क्लास में निकालना होगा। हम इसे, कहते हैं, `TransactionRecorder` कहेंगे:

class TransactionRecorder def initialize(options) @appointment_id = options[:appointment_id] @appointment_services = options[:appointment_services] @appointment_products = options[:appointment_products] @stylist_id = options[:stylist_id] @tip = options[:tip] end def run save_service_transaction_items(@appointment_services) save_product_transaction_items(@appointment_products) save_tip_transaction_item(@appointment_id, @stylist_id, @tip_amount) end def save_service_transaction_items(appointment_services) appointment_services.each { |s| s.save_transaction_item(appointment_id) } end def save_product_transaction_items(appointment_products) appointment_products.each { |p| p.save_transaction_item(appointment_id) } end def save_tip_transaction_item(appointment_id, stylist_id, tip) TransactionItem.create!( appointment_id: appointment_id, stylist_id: stylist_id, label: "Tip", price: tip, transaction_item_type_id: TransactionItemType.find_or_create_by_code("TIP").id ) end end 

फायदा

तब `Appointment` को सिर्फ इस तक कम किया जा सकता है:

class Appointment < ActiveRecord::Base has_many :appointment_services, :dependent => :destroy has_many :services, :through => :appointment_services has_many :appointment_products, :dependent => :destroy has_many :products, :through => :appointment_products has_many :payments, :dependent => :destroy has_many :transaction_items belongs_to :client belongs_to :stylist belongs_to :time_block_type def record_transactions transaction_items.destroy_all if paid_for? TransactionRecorder.new( appointment_id: id, appointment_services: appointment_services, appointment_products: appointment_products, stylist_id: stylist_id, tip: tip ).run end end end 

हम अभी भी `Appointment` में कोड संशोधित कर रहे हैं, जिसे टेस्ट नहीं किया जा सकता, लेकिन अब हम `TransactionRecorder` में सब कुछ टेस्ट *कर सकते हैं*, और चूंकि हमने प्रत्येक फ़ंक्शन को इंस्टेंस वेरिएबल्स के बजाय आर्गुमेंट्स स्वीकार करने के लिए बदल दिया है, हम प्रत्येक फ़ंक्शन को आइसोलेशन में भी टेस्ट कर सकते हैं। इसलिए हम अब शुरुआत की तुलना में बहुत बेहतर स्थिति में हैं।


  1. Ruby में Lambdas में महारत हासिल करें: बुनियादी से उन्नत तकनीकें तक Ruby में Lambdas में महारत हासिल करें: बुनियादी से उन्नत तकनीकें तक

    ब्लॉक Ruby का इतना महत्वपूर्ण हिस्सा हैं, उनके बिना भाषा की कल्पना करना कठिन है। लेकिन लैम्ब्डा? कौन लैम्ब्डा से प्यार करता है? आप वर्षों तक बिना एक का उपयोग किए जा सकते हैं। वे लगभग एक बीते युग के अवशेष लगते हैं। ...लेकिन यह पूरी तरह सच नहीं है। लैम्ब्डा की आस्तीन में कुछ दिलचस्प तरकीबें हैं एक बार

  1. रूबी एक्सेप्शन में TracePoint के साथ महारत हासिल करें: जटिल एरर परिदृश्यों का निदान करें रूबी एक्सेप्शन में TracePoint के साथ महारत हासिल करें: जटिल एरर परिदृश्यों का निदान करें

    कभी-कभी यह समझना वास्तव में मुश्किल हो सकता है कि एक्सेप्शन के साथ क्या हो रहा है - विशेष रूप से बड़े ऐप्स में। कल्पना करें कि आप किसी मौजूदा प्रोजेक्ट के अंदर कुछ कोड पर काम कर रहे हैं। आप एक एक्सेप्शन उठाते हैं, फिर कुछ अजीब होता है। शायद एक्सेप्शन निगल लिया जाता है। शायद एक एनवायरनमेंट वेरिएबल बद

  1. रूबी में स्टैटिक एनालिसिस में महारत: पार्सर जेम के साथ मेथड्स और पैरामीटर्स निकालें रूबी में स्टैटिक एनालिसिस में महारत: पार्सर जेम के साथ मेथड्स और पैरामीटर्स निकालें

    मान लीजिए कि आप अपने सोर्स कोड को पार्स करके अपने सभी मेथड्स ढूंढना चाहते हैं, वे कहां परिभाषित हैं और वे कौन से तर्क लेते हैं। आप यह कैसे कर सकते हैं? आपका पहला विचार इसके लिए एक रेगेक्स लिखना हो सकता है… लेकिन क्या कोई बेहतर तरीका है? हाँ! स्टैटिक एनालिसिस एक तकनीक है जिसका उपयोग आप तब कर सकते है