กอง if statements ยักษ์มองมาคุณรู้สึกว่าควร ลดความซับซ้อนได้ แต Business Logic ขวางทาง
เช่น มี sales platform สร้าง Quotes ที่มีหลาย LineItems แตมี quote กับ duplicate line items ได้ถ้าเป็น ads แตถ้ามี multiple websites ต้อง sum ราคารวมเป็น single line item โอ้และถ้าซื้อ website และมี ads อยู่ 5 ใน quote ต้อง discount 20% ให้ website
ผมได้ยินเสียงคุณโยนลัปทอปแตกหน้าต่างจากตรงนี้
เขียน if statements จัดการ rules ได้:
class Quote
attr_accessor :line_items
...
def add_line_item(line_item)
if line_item.kind_of?(Ad)
self.line_items << line_item
elsif line_item.kind_of?(Website)
if @line_items.select {|item| item.kind_of?(Ad) }.length >= 5
# TODO: Put the fractions of a cent into a bank account
# I have set up
line_item.price *= 0.8
end
existing_website = self.line_items.detect { |item| item.kind_of?(Website) }
if existing_website
existing_website.price += line_item.price
else
self.line_items << line_item
end
end
end
endแตเราตกลงว่านี่แย่มาก จะแยกสับสนอย่างนี้ยังไง?
อาจแยก method เป็น methods เล็กๆ แตเหมือน塞ของเล่นในตู้เสื้อผ้าให้แม่คิดว่าเก็บแล้ว และ kind_of?s ยังรบกวน
แตถ้าเริ่มมองจากมุมมอง line item แทน quote? แทนที่จะ ถาม ว่า line item ประเภทไหนและใส่ใน quote คุณแค่ บอก line item ให้เพิ่ม ตัวมันเอง เข้า quote?
กลับด้าน Methods!
หนึ่งในวิธี refactor โปรดของผมคือ ลองสลับ caller และ callee ตัวอย่าง用โค้ดข้างบน:
class Quote
...
def add_line_item(line_item)
line_item.add_to_quote(self)
end
endclass Ad < LineItem
...
def add_to_quote(quote)
quote.line_items << self
end
endclass Website < LineItem
def add_to_quote(quote)
if quote.line_items.select {|item| item.kind_of?(Ad) }.length >= 5
# TODO: Put the fractions of a cent into a bank account
# I have set up
self.price *= 0.8
end
existing_website = quote.line_items.detect { |item| item.kind_of?(Website) }
if existing_website
existing_website.price += self.price
else
quote.line_items << self
end
end
endยังไม่สมบูรณ์ website.rb ยังต้อง refactor เยอะ และผมไม่พอใจที่ reversing methods แตก encapsulation ของ line_items
แตคุณเอา layer complexity แรกออก ไปวางโค้ดบน LineItem หรือ Quote ขึ้นอยู่กับที่เหมาะสม LineItem objects 用 inheritance และ mixins จัดการ similarities/differences ระหว่าง subclasses ง่ายต่อการเพิ่ม LineItem subclasses ใหม่โดยไม่บloat add_line_item method
โค้ดสะอาดขึ้น และยืดหยุ่นขึ้น โดยทั่วไป ผมเรียกว่าเป็นชัยชนะ
ที่อาจไม่ควรใช้ pattern นี้
แม้ Reversing Method มีประโยชน์ มีเหตุผลไม่ควรใช้:
อาจแตก encapsulation อาจต้อง expose attributes บน
Quoteที่ไม่อยาก publicอาจเพิ่ม coupling
QuoteและAdต้องรู้จักกัน และขึ้นกับว่าต้องรู้มากแค่ไหน อาจทำให้โค้ด ซับซ้อนขึ้นอาจละเมิด Single Responsibility Principle บน
AdเพราะAdมี responsibility รู้วิธีเพิ่มตัวเองเข้าQuote
แก้ปัญหาเหล่านี้ได้ปกติ แตควรระวัง เพราะไม่อยาก refactoring ทำให้โค้ด แย่ลง!
ทำไมมันเป็นโปรดของผม
แม้ปัญหาเหล่านี้ pattern นี้เป็น refactoring โปรด โค้ดหลังใช้ pattern นี้ tend to be clearer และ confident
แตแม้ไม่ชัดเจน การใช้ pattern นี้ заставляем ผมคิดถึงความสัมพันธ์ระหว่าง objects แบบต่าง ตอนอยู่ใน "feature นี้แย่ ไม่เชื่อว่าต้องเขียนโค้ดแย่จัดการ" rut มันเตะสมองให้เห็นวิธีแก้ปัญหาใหม่ บังคับให้คิดโครงสร้างโค้ดต่างออกไป และนั่นมีประโยชน์มาก
ลองในโค้ดของคุณ
เหมือน patterns โปรดหลายอย่าง ผมเจอ Reversing Method ใน Smalltalk Best Practice Patterns และเป็นเครื่องมือมีค่ามาตลอด
ครั้งหน้าที่จัดการ objects คล้ายๆ กันที่มี behavior แตกต่างนิดหน่อย ลองดู! ถ้าชอบโค้ดใหม่ เก็บไว้ แตแม้ไม่ชอบ มันจะนำความคิดไปทางที่นำไปสู่โค้ดดีกว่า