কিছু লিগ্যাসি অ্যাপ্লিকেশনের সাথে কাজ করার সবচেয়ে চুলচেরা চ্যালেঞ্জগুলির মধ্যে একটি হল কোডটি পরীক্ষাযোগ্য হওয়ার জন্য লেখা হয়নি। তাই অর্থপূর্ণ পরীক্ষা লেখা কঠিন বা অসম্ভব .
এটি একটি মুরগি এবং ডিমের সমস্যা:উত্তরাধিকার প্রয়োগের জন্য পরীক্ষা লিখতে হলে, আপনাকে কোডটি পরিবর্তন করতে হবে, কিন্তু আপনি এটির জন্য প্রথম লেখা পরীক্ষা না করে আত্মবিশ্বাসের সাথে কোডটি পরিবর্তন করতে পারবেন না!
আপনি এই প্যারাডক্সের সাথে কিভাবে মোকাবিলা করবেন?
এটি মাইকেল পালকের চমৎকার লেগ্যাসি কোডের সাথে কার্যকরীভাবে কাজ করা-এ কভার করা অনেক বিষয়ের মধ্যে একটি। . আজ আমি স্প্রাউট ক্লাস বই থেকে একটি বিশেষ কৌশল জুম করতে যাচ্ছি .
কিছু লিগ্যাসি কোডের জন্য প্রস্তুত হন!
চলুন 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
এই ধরনের চোষা
উপরের কোডের সাথে দুটি সমস্যা আছে:
-
Appointment
অনেকগুলি বিভিন্ন দায়িত্ব রয়েছে (একক দায়িত্বের নীতির লঙ্ঘন), এই দায়িত্বগুলির মধ্যে একটি হল*লেনদেন রেকর্ড করাAppointment
-এ আরও লেনদেন-সম্পর্কিত কোড যোগ করে ক্লাস, **আমরা কোডটিকে একটু খারাপ করছি *। -
আমরা হয়তো একটি নতুন ইন্টিগ্রেশন পরীক্ষা লিখতে পারি এবং দেখতে পারি যে ইমেলটি এটিকে জুড়ে দিয়েছে, কিন্তু যেহেতু আমাদের কাছে
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
-এ সবকিছু পরীক্ষা করতে পারি , এবং যেহেতু আমরা ইনস্ট্যান্স ভেরিয়েবল ব্যবহার না করে আর্গুমেন্ট গ্রহণ করার জন্য প্রতিটি ফাংশন পরিবর্তন করেছি, আমরা এমনকি প্রতিটি ফাংশনকে বিচ্ছিন্নভাবে পরীক্ষা করতে পারি। তাই আমরা যখন শুরু করেছি তার থেকে এখন অনেক ভালো জায়গায় আছি।