Sandi Metz TRUE

18 Mar 2015

เขียนโค้ดมีคุณภาพตามแนวทาง Sandi Metz’s TRUE

TRUE ย่อมาจาก

  • Transparent
  • Reasonable
  • Usable
  • Exemplary

เมื่อไหร่ที่โค้ดเขียนตามนี้ ถือว่า ดีงาม

Transparent

อ่านง่ายและมีผลต่อการเปลี่ยน

สอง method ข้างล่างทำงานเหมือนกัน

Opaque

def ex(x)
  Account.transform(x * REQ_A).monkey_kick(:2, self)
end

Transparent

def extend(number_of_months)
  modified_month_count = number_of_months * loyalty_bonus

  Account.extend_subscription(modified_month_count)

  notify_accounting({action: extension, user: self})
end

โค้ด opaque ข้างบนยากต่อการเข้าใจ จู่ๆ ก็มีเลขมหัศจรรย์มาให้เฉย ชื่อ method ก็ไม่รู้ว่าหมายถึงอะไร แถมยังมีตัวแปร constant ที่ชื่อไม่มีความหมายเลยอีก

โค้ด transparent ที่เห็นอาจยังไม่ดีสุด ถึงงั้น่อย่างน้อยเข้าใจได้ดีกว่า เราจะเห็นผลของการเปลี่ยนแปลงจาก loyalty_bonus ได้ในตัวอย่าง โค้ดกำลังบอกเราว่า นี่เกี่ยวกับการเปลี่ยน accounting นะ แทนที่จะแปลกใจกับ money kick คืออะไรว้า?

Reasonable

ทุกการเปลี่ยนแปลงมีเหตุผล เมื่อโค้ดมันซับซ้อน

เหมือนเคย โค้ดข้างล่างนี่ทำงานเหมือนกัน

Unreasonable

def full_name
  salutation = gender == "M" ? "Mr" : "Ms"
  "#{salutation} #{first_name} #{last_name}"
end

Reasonable

def full_name(salutation: basic_salutation)
  "#{salutation} #{first_name} #{last_name}"
end

def basic_salutation
  salutation = gender == "M" ? "Mr" : "Ms"
end

เปลี่ยน salutation ควรเป็นเรื่องง่าย แต่โค้ด unreasonable กลับออกมาไม่ดีเท่าที่ควร กลับกันโค้ด reasonable ให้เราส่ง salutation อะไรก็ตามที่เราต้องการ หรืออย่างน้อยใช้ basic salutation แทน

Usable

เอาไปใช้ต่อที่อื่นได้

Unusuable

def square_number(number)
  number ** 2
end

Usable

class Numeric
  def power(x)
    self ** x
  end
end

square_number ในที่นี้กลับใช้ครั้งเดียว มันจะดีมากถ้าเราทำให้มันใช้กับตัวเลขทั้งหมดได้ ทีนี้เราเรียกมันใหม่ว่า power แล้วอยู่ภายใต้ Numberic แทน ซึ่งใช้ได้ดีแล้วล่ะ

Exemplary

โค้ดที่เขียนไปเป็นตัวอย่างของแนวโค้ดที่เราอยากให้เป็น

Unworthy

class Array
  def first
    self.reverse[1]
  end
end

Exemplary

class MyWeirdList
  include Enumerable

  def second_from_last
    collection[-2]
  end
  alias :first :second_from_last

  def collection
    @collection ||= []
  end
end

ตามแผนเลย? ใช่เลย แต่ว่าพอ google "monkey patch Array" และสิ่งที่แปลกใจ คือ มีอะไรแปลกๆ ที่คนต้องการยัดใส่ Array แล้วมันไม่มีประโยชน์โดยรวมซักเท่าไหร่ ถ้าเราเรียกมันว่า first method เราไม่ต้องการให้คนอื่นมาใช้โค้ดนี้ต่อแน่ๆ

อย่างน้อยที่สุดตัวอย่าง exemplary ก็จำกัดอยู่แค่ class เดียวที่เราตั้งใจต่างออกมา ดีกว่าที่จะเห็นในแบบแรกแน่ๆ

อ่านมาจาก Introducing Sandi Metz’s TRUE