Computer >> บทช่วยสอนคอมพิวเตอร์ >  >> การเขียนโปรแกรม >> Ruby

ลดความซับซ้อนโค้ด: มุมมองใหม่จัดการ Spaghetti Code

กอง 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 ตัวอย่าง用โค้ดข้างบน:

app/models/quote.rb
class Quote
  ...
  def add_line_item(line_item)
    line_item.add_to_quote(self)
  end
end
app/models/line_item.rb
class Ad < LineItem
  ...
  def add_to_quote(quote)
    quote.line_items << self
  end
end
app/models/website.rb
class 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 แตกต่างนิดหน่อย ลองดู! ถ้าชอบโค้ดใหม่ เก็บไว้ แตแม้ไม่ชอบ มันจะนำความคิดไปทางที่นำไปสู่โค้ดดีกว่า