ช่วงนี้กำลังอ่านหนังสือเล่มนี้อยู่ครับ Practical Object-Oriented Design in Ruby มันเป็นหนังสือที่ในช่วงครึ่งปีหลังของปี 2012 นี้ใครๆ ใน Ruby community ก็พูดถึงและแนะนำกันทุกคน เห็น user rating จากค่ายหนังสือต่างๆ ก็ไม่ต่ำกว่า 4.5 ดาว ทั้งนั้น
หนังสือเล่มนี้กล่าวถึงการดีไซน์ Object-Oriented แบบลงลึกละเอียดลึกถึงระดับตัวแปรเลยทีเดียว ตอนนี้ผมอ่านถึงบทที่ 3 รู้สึกว่าหนังสือเล่มนี้ให้ความรู้สึกเดียวกับตอนอ่าน Clean Code เลย แต่เน้นลงมาที่ตัว OO มากกว่า และผูกกับ syntax ของ Ruby นิดหน่อย มีบางจุดที่อาจจะใช้ไม่ได้ถ้าเขียนด้วยภาษาอื่น
ในหนังสือเล่มนี้มีกล่าวถึง dependencies ของ object 4 อย่าง อันได้แก่
- Class name
- Message name (method name)
- Arguments ของ message นั้น
- ลำดับของ Arguments เหล่านั้น
ถ้า class ที่ถูกเรียกใช้ต้องมีการเปลี่ยนแปลง 1 ใน 4 อย่างนี้ จะส่งผลให้เราต้องแก้ class ที่เรียกใช้ไปด้วย ซึ่งแน่นอนว่า class ที่เรียกใช้นี้ก็อาจจะถูกคนอื่นเรียกใช้ต่ออีกทอด ซึ่งจะทำให้เกิดการต้องแก้ไขต่อเนื่องเป็นทอดๆ ไป ทำให้คนไม่อยากแก้ไข ถ้า code เน่าก็ปล่อยให้เน่าไปเช่นนั้น
3 ข้อแรกจาก 4 ข้อนั้น ผมเห็นด้วยทันทีตอนที่อ่าน แต่สำหรับข้อ 4 ผมมีข้อที่คลางแคลงใจนิดหน่อยจะมาเล่าให้ฟัง ลองดู code นี้ก่อนครับ
class A def a(b, c, d) @b = b @c = c @d = d end ... end
ผู้เขียนกล่าวว่าถ้า class A นี้ถูกเรียกใช้จากโปรแกรมเมอร์คนอื่น ปัญหาหนึ่งที่เราจะพบคือ เราจะไม่สามารถแก้ลำดับของตัวแปร หรือเพิ่มลดตัวแปรโดยไม่ให้กระทบผู้เรียกใช้ได้ เป็นผลให้การแก้ไข code ชุดนี้เพื่อไป reuse ในที่อื่นๆ ทำได้ยากขึ้น วิธีการที่ผู้เขียนแนะนำคือให้รับ arguments เป็น Hash แทน ดังนี้
class A def a(args) @b = args[:b] @c = args[:c] @d = args[:d] end ... end
เมื่อทำเช่นนี้แล้ว เราจะมีอิสระมากขึ้นได้การเพิ่มลด argument ไม่ให้กระทบผู้ที่เรียกใช้งานอยู่ แต่มันกลับเป็นวิธีการที่ผมไม่เห็นด้วยในช่วงเวลาแรกที่อ่าน แต่พอได้ลองคิดดูผมก็เข้าใจอะไรบางอย่างมากขึ้น
สาเหตุที่ผมไม่เห็นด้วยกับการส่ง hash เป็น arguments เพราะผมเคยมีประสบการณ์กับ code Java ประมาณนี้
public class A { private B b; private Map options; public A(int x, int y, Map options) { b = new B(options); this.options = options; ... } public a() { int i = b.m(); int j = i + options.get("something"); ... } } public class B { private C c; private Map options; public B(Map options) { c = new C(options); this.options = options; } public int b() { options.get("anotherThing"); ... } }
การที่ options ถูก pass ต่อไปเรื่อยๆสู่ class ที่เรียกใช้ และแต่ละ class รายทางก็ดึงค่าบางอย่างออกมาใช้ มันสร้างปัญหาทำให้เราไล่ code ยากว่าจริงๆ หากเราต้องการเรียกใช้ A เนี้ย เราต้อง pass key-value อะไรให้ options บ้าง เพราะเราต้องไปไล่ code ลงไปตาม class ที่ A ส่ง options ไปให้ทั้งหมดลงไปเรื่อยๆ นี่เป็นสาเหตุที่ทำให้ผมไม่ชอบการ pass Map(Hash) เป็น argument ตั้งแต่นั้นเป็นต้นมา ผมคิดว่าผมขอเขียนแบบประกาศตัวแปรให้รู้ๆ กันไปเลยดีกว่า A ต้องการอะไร
แต่คุณเห็นความแตกต่างระหว่างการใช้ args ของ code ชุดที่ 2 และ options จากชุดที่ 3 มั้ยครับ ใน code ชุดที่ 2 ที่ผมแปลงมาจากในหนังสือนั้น args ไม่ได้ถูกส่งต่อ ค่าทั้งหมดที่มากับ hash นั้นจะถูกดึงออกมาเก็บไว้ใน instance variable ทั้งหมด แล้ว hash นั้นก็จะถูกทิ้งไป ปัญหาที่ทำให้ผมไม่ชอบการ pass hash เป็น argument ก็จะไม่เกิด จุดนี้ทำให้ผมเข้าใจและเห็นด้วยกับแนวทางที่ผู้เขียนเสนอขึ้นมา ผู้เขียนยังเสนอการต่อยอดจาก code ชุดที่ 1 และ 2 อีกดังนี้ครับ
class A def a(b, c, d) @b = b @c = c @d = d end ... end class FactoryA def self.createA(args) A.new(args[:b], args[:c], args[:d]) end end
จาก code นี้เราจะเปิด FactoryA เป็น external api รับ args เป็น hash จากนั้นส่งต่อให้ implementation ภายในคือ class A อีกที การเขียน code แบบนี้ทำให้ public api เราคงที่ ในขณะที่เราสามารถแก้ไข เปลี่ยนแปลง refactor internal implementation ของเรา (A) ได้เต็มที่โดยไม่ต้องกังวลว่าจะกระทบกับคนที่เรียกใช้มัน
การจะเลือกใช้แนวทางใดผมว่าเป็นการชั่งน้ำหนักระหว่าง การบังคับ และ วินัย ครับ ถ้าเราอยู่ในที่ๆ คนไม่ยอมมีระเบียบวินัย(ส่ง options ไปทั่ว)เราก็ต้องใช้การบังคับแทน(การรับ argument แบบตายตัว)เพื่อที่จะไม่เกิดความวุ่นวาย แต่ถ้าเราอยู่ในจุดที่คนมีระเบียบวินัย (ไม่ส่ง args ต่อๆไป)แล้ว เราสามารถสร้างสรรค์อะไรดีๆ ได้อีกมากครับ
แนวคิดในช่วงนี้ของผมค่อนข้างมุ่งไปในทางนี้ดังที่เคยเขียนไปไว้เรื่อง องค์กรเปิด
การที่เราจะสร้างสังคม องค์กร ที่เจ๋งๆได้นั้น ผมว่าเราควรให้พลังและอำนาจแก่คนในองค์กรอย่างเต็มที่ เพื่อให้เค้าได้ใช้ความคิดสร้างสรรค์ผลิตสิ่งดีๆ ออกมา แต่ทั้งนี้ทั้งนั้นมันก็มีข้อจำกัดเช่นกันว่า คนในสังคม องค์กรนั้นๆ จะมีความรับผิดชอบและระเบียบวินัยเพียงใด ถ้าคนในสังคมและองค์กรของคุณมีศักยภาพ ผมอยากให้ๆอิสระแก่พวกเค้าครับ แล้วคุณอาจจะได้อะไรดีๆกลับมาอย่างคาดไม่ถึง
Update: ทั้งหลายทั้งปวงจะบอกว่านี่ไม่ได้หมายความว่าเราควรส่ง argument เป็น hash เสมอไปนะครับ เลือกใช้ให้เหมาะสมกับเวลาและโอกาสครับ อย่างกรณี hash นี้เหมาะกับการเขียน external api ให้คนอื่นมาเรียกใช้ครับ ขอขอบคุณพี่แน็ทที่ทักท้วง