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

स्प्राउट क्लासेस का उपयोग करके रूबी को रिफैक्ट करना

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

यह चिकन और अंडे की समस्या है:लीगेसी एप्लिकेशन के लिए परीक्षण लिखने के लिए, आपको कोड बदलना होगा, लेकिन आप इसके लिए पहले परीक्षण लिखे बिना कोड को आत्मविश्वास से नहीं बदल सकते!

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

यह माइकल फेदर्स के उत्कृष्ट विरासत कोड के साथ प्रभावी ढंग से कार्य करना में शामिल कई विषयों में से एक है। . आज मैं स्प्राउट क्लास . नामक पुस्तक से एक विशेष तकनीक पर ज़ूम इन करने जा रहा हूं ।

कुछ लीगेसी कोड के लिए तैयार हो जाएं!

आइए इस पुराने ActiveRecord वर्ग को देखें जिसे Appointment . कहा जाता है . यह काफी लंबा है, और वास्तविक जीवन में यह 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 # New code
  end
end

def send_thank_you_email_to_client
  ThankYouMailer.thank_you_email(self).deliver
end

उस तरह का बेकार

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

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

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

इसे अलग करना बेहतर है

नया कोड इनलाइन जोड़ने से बेहतर समाधान लेनदेन-रिकॉर्डिंग व्यवहार को अपनी कक्षा में निकालना होगा। हम इसे कहते हैं, 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. रूबी में लैम्ब्डा का उपयोग करना

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

  1. रूबी में जटिल अपवाद व्यवहार का पता लगाने के लिए ट्रेसपॉइंट का उपयोग करना

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

  1. रूबी में स्थैतिक विश्लेषण

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