The Open-Closed Principle เปลี่ยนพฤติกรรม Code โดยไม่แก้ไข Code ทำยังไงหละนี้ (ตอน 2)

imKrish Developer
imKrish
Published in
5 min readNov 10, 2016

สวัสดีครับ บทความนี้ก็เป็นตอนที่ 2 ของ The Open-Closed Principle นะครับ เพราะฉะนั้นแนะนำให้ทุกท่านไปอ่านตอนที่ 1 ก่อนนะครับ (ใครที่อ่านมาแล้วก็แนะนำให้กลับไปอ่านครับ 555+ เพราะเนื้อหามันต่อกัน บทความสั้นๆครับประมาณ 5 นาทีเท่านั้นเอง)

  • ภาษาที่ใช้เขียนคือ TypeScript นะครับ
  • TypeScrip (จาก Microsoft) เป็นภาษา Superset ของ Javascript ครับ คือ มีทุกอย่างที่ Javascript มี แต่เพิ่มความสามารถบางอย่างเข้าไป หลักๆก็คือเพิ่มความสามารถของ Static Type แล้วก็ OOP ให้กับ Javascript ครับผม (Angular2, NativeScript ก็เขียนบนภาษานี้ครับ)

จากตอนที่แล้วโปรแกรมเล่นเสียงดนตรีของเราก็มีรูปร่างดังรูปนี้ครับ

เรามี Instrument(เครื่องดนตรี) interface ที่ Class ของเครื่องดนตรีทุกชนิดต้อง Implement มันก่อนครับ (เหตุผลเพราะอะไร ไปอ่านตอนที่ 1 นะครับผม)

ตอนนี้ผมก็มีเครื่องดนตรี 4 ชนิดครับ คือ IndianLute(พิณ) Fiddle(ซอ) Dulcimer(ขิม) และ Flute(ขลุ่ย)

แล้วก็ Function ที่เอาไว้เล่นเครื่องดนตรีพวกนี้ครับ

จะเห็นว่าตอนนี้เราไม่สนใจแล้วนะครับ ว่าเครื่องดนตรีนั้นเป็นประเภทอะไร (ดีด สี ตี เป่า) แค่รู้ว่ามันเล่นได้ก็พอครับ (ก็คือการ Abstract โดยใช้ Interface นั่นเอง)

เวลาที่เราจะใช้ Function นี้ เพื่อที่จะให้มันเล่นเสียงดนตรีก็ตามนี้ครับ

  • สร้าง variable ที่เป็น Array ไว้เก็บ Object หลายๆตัวของเครื่องดนตรี
  • ผ่าน variable ตัวนี้ไปยัง playAllInstruments function ครับ ดังรูปเลย
  • เสริมนิดนึงครับสำหรับคนอยากรู้วิธีการใช้ var, let, const ใน javascript แนะนำให้อ่านบทความนี้ดูครับ
  • จากรูปนี้เราจะเห็นได้ว่า playAllInstruments function จะเล่นดนตรีตามลำดับของ Object ใน Array instruments ที่ผ่านเข้ามาครับ

หลังจากส่ง Software ตัวนี้ให้ลูกค้า ลูกค้าก็ใช้งานได้ราบรื่นดีครับ ไม่มีปัญหาอะไร จนผ่านไป 6 เดือน ลูกค้าอยู่ดีดีก็มาหาที่บริษัทครับ(มากับ Requirement ใหม่แน่ๆ) แล้วก็บอกว่า อยากให้ Software ตัวนี้จัดอันดับการเล่นของเครื่องดนตรีแต่ละชนิดได้ด้วย (นั่นไงว่าแล้วว)

ในฐานะของ Programmer ตอนนี้คงมีสบถอยู่ในใจกันมั่งหละครับ เปลี่ยนอีกแล้วหรอเนี่ยยยย อุตส่าห์ Apply OCP ไปแล้ว(จากตอนที่ 1) ไม่คิดว่าจะต้องมายุ่งกับ function นี้อีกแล้ว OMG

555+ คือจะบอกว่าไม่มีอะไรที่เป็น Ideal หรือ Perfect ครับ ต่อให้เราจะใช้กี่ Principles กี่ Patterns แต่ด้วยอำนาจของ Requirement ใหม่ๆ จากลูกค้าแล้ว มันก็ยังมีโอกาสที่ทำให้เราต้องไปแก้ Code ในส่วนนั้นอยู่ดีครับ

“ Fool me once, shame on you. Fool me twice, shame on me. ”

ประเด็นสำคัญมันอยู่ตรงนี้ครับ เวลาเขียน Software ขึ้นมา อย่าเขียนมันเพื่อ Changes ที่ยังไม่เกิดครับ ให้รอ Changes ที่มาจากลูกค้าก่อน(ครั้งแรก) แล้ว Apply OCP ใน Code ส่วนนั้นของเราครับ เท่ากับว่าต่อไป(ครั้งต่อๆไป) ถ้าลูกค้าต้องการให้เราเปลี่ยนแปลงพฤติกรรมของ Code เราในแนวนี้อีก ใน Requirement หน้า เราไม่ต้องกลับมาแก้ไข Code ในส่วนนี้อีกแล้ว แค่เขียน Code เพิ่ม (Open for extension, closed for modification)

อาจจะยังไม่เห็นภาพ ยกตัวอย่าง ในตอนที่ 1 ลูกค้า Require เข้ามาคือต้องการเพิ่มประเภทของเครื่องดนตรี เราก็ทำการ Apply OCP ไปใน Code ที่เกี่ยวข้อง พอครั้งที่ 2 ลูกค้าต้องการเพิ่มเครื่องดนตรีประเภทอื่นๆอีก จะเห็นได้ว่าเราไม่ต้องกลับไปแก้ไข Code ใน playAllInstruments function อีกต่อไปแล้ว

แต่ในตอนที่ 2 นี้ ลูกค้า Require ให้เราเล่นเครื่องดนตรีตามลำดับ ซึ่งมันเป็น Requirement ใหม่เลย(การจัดอันดับ ไม่ได้เกี่ยวข้องกับ การเพิ่มชนิดของเครื่องดนตรีเลย) นี้แหละครับก็คือเหตุผลที่ทำให้ผมต้องกลับไปยุ่งกับ playAllInstruments function อีกครั้งนึง เพื่อทำให้มันจัดอันดับการเล่นได้

เพราะฉะนั้น สิ่งที่เราต้องทำต่อไปก็คือ ใช้ OCP apply ใน requirement ใหม่นี้ แล้วถ้าต่อไปลูกค้าต้องการจัดอันดับการเล่นของเครื่องดนตรีในตำแหน่งอื่นๆ เราไม่จำเป็นต้องมาแก้ไข Code ของเราอีกต่อไปแล้ว

อีกครั้งครับ

“ Fool me once, shame on you. Fool me twice, shame on me. ”

ให้ลูกค้า fool เราแค่ครั้งเดียวพอครับ ถ้าต่อไปมี Requirement แนวนี้มาอีก เรานั่งยิ้มได้เลย ทำการแก้ไขแปปเดียว แต่เราอาจจะบอกลูกค้าว่าสัก 1 เดือนก็ได้นะ 555+

ร่ายมาซะยาวตอนนี้ท่านผู้อ่านคงคิดในใจว่า แล้วเมื่อไรคุณเมิงจะแก้ไขปัญหาหละเนี่ย เอาหละครับ เรามาเริ่มกันเถอะ หลังจากนั่งคุยกับลูกค้าอยู่เป็นชั่วโมงผมก็ได้ Requirement ใหม่มาดังนี้

  • ให้เล่น ขลุ่ยเป็นเครื่องดนตรีชิ้นแรก
  • ให้เล่น ขิมเป็นเครื่องดนตรีชิ้นที่สอง
  • เครื่องดนตรีอื่นๆ ให้เล่นหลังจากเครื่องดนตรี 2 ตัวนี้ (ตำแหน่งไหนก็ได้)
  • เครื่องดนตรีมีได้แค่ 1 ชิ้นเท่านั้น

นั่งๆคิดดูมันก็คล้ายกับ การจัดอันเพลงใน Playlist เลยนะครับ เพลงที่ถูกจัดอันดับจะเล่นตามลำดับที่เราต้องการ ส่วนเพลงที่เหลือเล่นแบบ random ครับผม

ต่อมาผมก็มานั่งแปลความเป็นภาษา Programming อีกที ถ้าผมผ่าน variable นี้เข้าไปใน playAllInstruments function ของผม

โปรแกรมของผมจะต้องเล่นเสียงดนตรีในลำดับดังนี้ครับ

// (1)                                      
flute.play() // ขลุ่ย
dulcimer.play() // ขิม
indianLute.play() // พิณ
fiddle.play() // ซอ
// (2)
flute.play() // ขลุ่ย
dulcimer.play() // ขิม
fiddle.play() // ซอ
indianLute.play() // พิณ

จะเห็นได้ว่า พิณ กับ ซอ จะเล่นในลำดับไหนก็ได้ครับ ขอให้มันเล่นหลัง ขลุ่ย กับ ขิม ก็พอ เอาหละครับ Requirement มาเท่านี้ ก็ทำเท่านี้พอครับ

สิ่งต่อไปที่เราต้องคิดคือถ้าต่อไป ลูกค้าอยากให้เล่น ขิม ก่อน ขลุ่ย หละ หรือ อยากให้เล่น ซอ พิณ ขิม ลำดับตามนี้หละ “เราจะ apply OCP ยังไงในรอบนี้เพื่อให้รอบหน้าเราไม่ต้องมาแก้ไข Code ในส่วนนี้อีก ถ้าเกิดมี Requirement แนวเดิมๆ แนวนี้เข้ามาอีก”

ทำไงดี ทำไงดี ทำไงดี? หลังจากนั่ง เปิด google, stackoverflow บลา บลา บลา ผมก็ได้คำตอบดังนี้

ผมจะใช้ Table-Driven Approach นะครับผม มองให้มันเป็นตารางนะครับ

Flute(ขลุ่ย)อยู่แถวที่ 1, Dulcimer(พิณ)อยู่แถวที่ 2

จะเห็นได้ว่าใน function playAllInstruments ของผมมีเพิ่มมา 1 บรรทัดครับ คือ

const orderedInstrument = sortInstrumentsByPlaylist(instruments)

ก็คือพอรับ instruments มาก็เอาไป sort ที่ sortInstrumentsByPlaylist function ก่อนครับ

สร้าง getClassNameOf function ขึ้นมา Refactoring เล็กน้อยครับให้ Code มันอ่านง่ายขึ้นนิดหน่อย 555+

const getClassNameOf = function (obj: Object) {
return obj.constructor.name
}
  • ใครสนใจเรื่อง Higher-Order Functions (พวก forEach, map, sort, reduce) ใน javascript แนะนำบทความนี้ครับ ดีมากๆ เลย

จะบอกว่าเสร็จแล้วครับ :)

ที่ต้องทำหลักๆคือสร้าง function ขึ้นมา sort โดยใช้ ค่าจากใน playlist แล้ว return เครื่องดนตรีที่จัดอันดับเรียบร้อยแล้วออกไปครับ

คราวนี้เรามาดูประสิทธิผลกันครับ

หลังจากเสร็จผมก็ส่งมอบงานให้ลูกค้าเรียบร้อยครับ ลูกค้าก็ใช้โปรแกรมตัวนี้ไปอย่าง Happy ครับ เหมือนจะจบอย่าง Happy Ending แต่ ยังครับยัง !!!

ผ่านไป 3 เดือน ลูกค้าเข้ามาที่บริษัท(อีกแล้ว 555+) พร้อมมอบหมาย Requirement ใหม่ให้เรา ดังนี้

  • เพิ่มให้เล่นเสียงกีตาร์ได้
  • จัดอันดับการเล่นใหม่ดังนี้ กีตาร์ ขลุ่ย ซอ

ใครที่อ่านมาถึงตรงนี้แล้วเข้าใจ จะเห็นได้ว่า ผมนี้นั่งยิ้มเลยครับ แต่ก็ทำหน้าเครียดบอกลูกค้าไปว่า ขอเวลาเดือนนึง !!! (เป็นตัวอย่างที่ไม่ดีอีกแล้ว)

หลังจากลูกค้าเดินออกไป ผมก็เปิด Youtube ฟังเพลง นั่งเล่น Facebook สบายใจอยู่ 30 นาที แล้วก็เริ่มแก้ไขโปรแกรมของผมดังนี้

  1. เพิ่ม Class Guitar แล้ว implement Instrument ใส่รายละเอียดใน method play

2. จัดลำดับ playlist ใหม่

เสร็จครับ เสร็จแล้ว บอกลูกค้าไปเดือนนึงซะงั้น

ในข้อหนึ่งนี้ที่ code เพิ่ม Class Guitar เข้าไป นี้คือเป็นไปตามOCP Principle นะครับ (Open for extension) แต่ในข้อ 2 นี้ มัน break rule นี้หว่า (Closed for modification) จะเห็นได้ว่ามันต้องกลับมาแก้ไข Code ใน playlist const นี้ !!!

  • ผมจะบอกว่ามันคุ้มค่า ที่ทำแบบนี้ครับ มองมันให้เป็นตารางครับ มันเป็นแค่ const จะเห็นได้ว่า ไม่มี Logic อะไรเข้ามาเกี่ยวข้องเลย แค่แก้ไข เรียงลำดับเครื่องเล่นให้เป็นตามที่เราต้องการแค่นั้นเอง
  • อีกอย่างนึงคือจะเห็นได้ว่ามันเป็น Module ที่ไม่มี Dependency เลยครับ เป็น Module โดดๆ เพราะฉะนั้นการเปลี่ยนแปลงใน Module นี้ไม่ได้ส่งผลกับ Module อื่นๆเลยครับผม
  • มันเป็น trade off ครับ คือมีได้ก็ต้องมีเสีย แต่ในที่นี้คือได้มากกว่าเสียเยอะมาก ลองนึกภาพถ้าเราต้องไปนั่งแก้ไขที่ sortInstrumentsByPlaylist function ทุกครั้งที่มีการแก้ไขลำดับการเล่นของเครื่องดนตรี แค่คิดนี้ ผมก็ปวดหัวละครับ

สุดท้ายนี้ ไม่มีอะไรที่เป็น Ideal หรือ อุดมคติครับ เราต้องรู้จักพลิกแพลงบ้างครับ

การทำตามกฎ ตาม Principle จะบอกว่ามันดีครับ แต่เมื่อไรที่เราเห็นว่าการ Break กฎ หรือ Principle มันมีข้อดีมากกว่าข้อเสีย เราจะทำตามกฎเพื่ออะไรหละครับ ??? จริงไหมหละครับ

“ เพราะฉะนั้นทำอะไร เราต้องมีสติครับ ไม่ใช่น่ามืดตามัว ตามกฎ ตามคนอื่น คนรอบข้างไปซะทุกอย่าง ในชีวิตจริงก็เหมือนกันนะ ”

The Open-Closed Principal ก็ขอจบลงเพียงเท่านี้นะครับผม หวังว่าท่านผู้อ่านจะได้รับความรู้ ได้ Concept ใหม่ๆไม่มากก็น้อยนะครับ ขอบคุณที่อ่านจนจบครับผม ถ้ามีอะไรอยากแนะนำ หรือติชมก็เต็มที่ได้เลยครับ โพสที่ comment ข้างล่างได้เลย ยินดีรับฟังทุกความเห็นครับผม

ส่วนในบทความหน้าเรามารู้จักกับตัว L ใน SOLID กัน จะเป็นอะไรรอติดตามนะครับผม : )

ฝาก Fanpage ด้วยครับผม: https://www.facebook.com/imkrish.developer/

สำหรับ Open-Closed Principle ตอนที่ 1 นะครับ อ่านได้ที่นี้เลยครับ

สำหรับใครอยากรู้ว่าทำไมเราควร Apply SOLID Principlesใน Software ของเรา อ่านที่บทความนี้ได้เลยครับ

สำหรับ Principle แรกของ SOLID: The Single-Responsibility Principle อ่านได้ที่นี้จ้า

Reference:

Robert cecil martin. (2002). Agile Software Development, Principles, Patterns, and Practices. (1st ed.). England: Pearson.

“ Happiness Only Real When Shared ”

ที่เริ่มเขียน Blog เพราะไปดูหนังเรื่องนึงแล้วเจอ Quote นี้ ผมก็เลยจะลองดูครับว่ามันจริงหรือเปล่า

--

--

imKrish Developer
imKrish

I’m going to be the best I could be, not someone tells me I should be. I am optimistic and I love freedom : )