কম্পিউটার

স্প্রাউট ক্লাস ব্যবহার করে রিফ্যাক্টরিং রুবি

কিছু লিগ্যাসি অ্যাপ্লিকেশনের সাথে কাজ করার সবচেয়ে চুলচেরা চ্যালেঞ্জগুলির মধ্যে একটি হল কোডটি পরীক্ষাযোগ্য হওয়ার জন্য লেখা হয়নি। তাই অর্থপূর্ণ পরীক্ষা লেখা কঠিন বা অসম্ভব .

এটি একটি মুরগি এবং ডিমের সমস্যা:উত্তরাধিকার প্রয়োগের জন্য পরীক্ষা লিখতে হলে, আপনাকে কোডটি পরিবর্তন করতে হবে, কিন্তু আপনি এটির জন্য প্রথম লেখা পরীক্ষা না করে আত্মবিশ্বাসের সাথে কোডটি পরিবর্তন করতে পারবেন না!

আপনি এই প্যারাডক্সের সাথে কিভাবে মোকাবিলা করবেন?

এটি মাইকেল পালকের চমৎকার লেগ্যাসি কোডের সাথে কার্যকরীভাবে কাজ করা-এ কভার করা অনেক বিষয়ের মধ্যে একটি। . আজ আমি স্প্রাউট ক্লাস বই থেকে একটি বিশেষ কৌশল জুম করতে যাচ্ছি .

কিছু ​​লিগ্যাসি কোডের জন্য প্রস্তুত হন!

চলুন 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 # New code
  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 শুধু এই জন্য নিচে pared পেতে পারেন:

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. RuboCop সহ রুবি কোড লিন্টিং এবং স্বয়ংক্রিয় ফর্ম্যাটিং

  2. রুবিতে ল্যাম্বডাস ব্যবহার করা

  3. রুবিতে জটিল ব্যতিক্রম আচরণ অন্বেষণ করতে ট্রেসপয়েন্ট ব্যবহার করে

  4. রুবি মধ্যে স্ট্যাটিক বিশ্লেষণ