การเปลี่ยน JavaScript if-else และ try-catch ให้เป็นรูปแบบ Functional

16-ส.ค.-19

คัมภีร์เทพ IT

สำหรับ Programmer เชื่อว่าคงคุ้นเคยหรือใช้งานคำสั่ง if-else และ try-catch มาบ้างแล้ว โดยในบทความนี้จะกล่าวถึง การเปลี่ยน if-else และ try-catch ใน JavaScript ให้เป็นรูปแบบ Functional

“การสร้างรูปแบบ Imperative ในภาษา (If-else/Try-catch) ให้สามารถ Declarative ได้มากขึ้น จะช่วยปรับปรุงความสามารถในการอ่านและ Test Code ของคุณ” แม้มันจะเป็นความเห็นที่ได้ชัดเจน แต่ก็ไม่ควรไปยึดถือมันมากนักเพราะมันก็ต้องใช้อย่างระมัดระวัง

Abstractions นั้นอาจเป็นเรื่องยาก เพราะมันบังคับให้คุณและทีมของคุณต้องทำตามความเห็นของคนส่วนใหญ่ และการที่จะให้ทุกคนเห็นด้วยนั้นเป็นเรื่องยาก โดยเฉพาะอย่างยิ่งเมื่อคุณพยายามที่จะ Rewrite รูปแบบภาษาที่เรียบง่ายให้เป็น Functional Abstractions และมันก็ยิ่งยากอีกเท่าตัว เนื่องจากยังมีประเด็นเรื่องการอ่านง่ายของ Code และ Level ของ Unit Test เข้ามาเกี่ยวข้อง ซึ่งเรื่องนี้ถือเป็นเรื่องความรู้สึกส่วนบุคคล

Functional construct #1: conditionally

เรามาลองพิจารณาการแปลงพื้นฐานอย่าง If-else กัน โดยจะเป็นอย่างไรถ้าเราจะทำให้ If-else เป็นในรูปแบบของ Functional?

การ Implementation จะมีลักษณะดังนี้:

คำถามที่น่าสนใจคือ เราจะสามารถเขียน If-else ในรูปแบบที่ ไม่ว่าจะเงื่อนไข True หรือ False ที่จะ Return ค่าด้วยการ Evaluate Function ได้หรือไม่? หรือพูดอีกอย่างก็คือ ถ้าเราสามารถแสดงถึงสิ่งที่ If-else ทำใน Pure Function ได้ มันจะมีลักษณะอย่างไร?

มันต้องใช้การ Config ซึ่งมี 3 Functions คือ If(), Then() และ Else() และมันสร้าง Function ที่สามารถรับ Props Argument ของคุณ

ถ้า if(props) มีการ Evaluate ค่าเป็น True มันจะไปสู่ then(props) หรือไม่ก็ else(props) ซึ่งทั้ง 3 Functions ได้รับ Input เดียวกันและสร้าง Result ประเภทเดียวกัน

หากคุณใช้ TypeScript เราสามารถบังคับ Input Type และ Result ด้วย Generics หากสิ่งนี้ดูซับซ้อนเกินไป หรือคุณไม่มีประสบการณ์เกี่ยวกับ Generics ใน TypeScript คุณสามารถข้ามตัวอย่างต่อไปนี้ได้

ลองพิจารณาเงื่อนไข If-else ดู:

ตัวอย่างข้างต้นเกือบเป็นวิธีการเขียนที่ Perfect แล้ว แต่เราสามารถทำได้ดีกว่านั้น ตอนนี้อยากให้ลองดูวิธีการเขียนด้วย conditionally ตามด้านล่างดู:

ตอนนี้ Concern ต่าง ๆ ถูกจัดการโดย 2 Functions ที่แตกต่างกัน ซึ่ง conditionally ได้บังคับให้คุณแยก Concern ออกจากกันอย่างค่อยเป็นค่อยไป ในทางกลับกัน นี่เป็นทางเลือกให้คุณได้ Test แต่ละ Concern แยกเป็นส่วน ๆ และ Conditionally เองก็ทำหน้าที่แบบนั้น ซึ่งเป็นไปตาม F.I.R.S.T principles ของ Unit Testing

เมื่อคนอื่นอ่าน Code ของคุณเพื่อทำความเข้าใจกับสิ่งที่ getCarConfig ทำ พวกเขาไม่จำเป็นต้องลงไปที่รายละเอียดการ Implement ของ priceChange และ getDescription เพราะคุณตั้งชื่อที่เข้าใจได้แล้ว ตอนนี้การแยกเป็นส่วน ๆ ของคุณทำหน้าที่เพียงอย่างเดียว(Single Responsibility) และการตั้งชื่อที่เหมาะสมจะเป็นการสร้างความประหลาดใจน้อยที่สุด(Least Astonishment) ให้กับคนที่ได้อ่านมัน

นั่นคือเหตุผลที่ฉันสนับสนุนการนำ Functional Programming มาใช้ใน JavaScript มันบังคับให้คุณแบ่งปัญหาออกเป็นส่วนเล็ก ๆ ที่เรียกว่า Function ซึ่ง Function เหล่านี้:

  1. แยก Concern ออกเป็นส่วน ๆ
  2. ช่วยให้การ Test ทำได้ดียิ่งขึ้น
  3. เป็นไปตามหลักการ Single Responsibility
  4. ด้วยการฝึกฝนเล็ก ๆ น้อย ๆ ในการตั้งชื่อสิ่งต่าง ๆ จะทำให้เป็นไปตาม หลักการของ Least Astonishment

Functional construct #2: tryCatch

Exceptions ถือเป็นเครื่องมือที่ทรงพลังในหลาย ๆ ภาษา พวกมันจะจัดการขอบเขตที่ ไม่รู้จัก (Unknown), ไม่สามารถหาเหตุผลได้ (Unreasonable) และ ไม่ปลอดภัย (Unsafe) ของ System

ใน JavaScript คุณสามารถใช้ Try-catch ได้ดังนี้:

หากคุณต้องการบันทึก State (เช่น storedSuccessfully) คุณต้องทำการ Declare เพื่อเป็นการส่งสัญญาณการ Mutation ของ State ที่เป็นไปได้เช่นเดียวกับในตัวอย่าง ตามความหมายแล้ว Try-catch จะเป็นสัญญาณการหยุดการควบคุม Flow และทำให้การอ่าน Code นั้นยากขึ้น

ลองมาสร้าง Functional Utility เพื่อลดปัญหาเหล่านั้นกัน:

จากตัวอย่าง เราทำการ Encapsulate โครงสร้างของ Try-catch ใน Function ซึ่ง tryCatch() จะรับ Config Object ด้วย 2 Functions จากนั้นจะ Return Function ซึ่งจะยอมรับ Single Props Object

  1. tryer(props) จะถูก Evaluate และ Return Result กลับมา
  2. ขณะที่ทำ tryer(props) หากมี Exception เกิดขึ้น catcher(props) ก็จะถูกเรียกใช้

ด้วย TypeScript คุณสามารถใช้ Generics เพื่อบังคับใช้ Input Type และ Output Type ของรูปแบบนี้

หากคุณรู้สึกกังวลในเรื่องการใช้ Generics เรามาลอง Refactor ตัวอย่างก่อนหน้านี้กัน

เราจะเห็นว่ารูปแบบ Functional ของเราบังคับให้เราแยกส่วนที่ไม่ปลอดภัยของ Code ออกเป็น Function ต่าง ๆ  นอกจากนี้เราลงเอยด้วย 3 Functions ที่แยกจากกันโดยสิ้นเชิง ซึ่งเราสามารถ pipe() เข้าด้วยกันเพื่อให้ได้ Result ของเราออกมา

จากข้อดีที่ได้อธิบายไปก่อนหน้านี้ สามารถนำมาใช้ในส่วนนี้ได้เช่นกัน สิ่งที่สำคัญที่สุดคือ เรื่องความสามารถในการอ่าน ตอนนี้เมื่อมีคนมาอ่าน setUserLangage Function ของคุณ พวกเขาไม่จำเป็นต้องลำบากในการวิเคราะห์ Try-catch ล่วงหน้า เพราะมันถูก Encapsulate ด้วย storeLanguageCode Function

ปิดท้ายบทความ

ไม่สนับสนุนให้คุณใช้ conditionally และ tryCatch เพียงเพราะแค่เห็นว่ามันมีประโยชน์ บางครั้งการใช้ Ternary Operation ที่เรียบง่าย หรือ If-else ก็สามารถทำให้อ่านเข้าใจได้โดยง่าย แต่แนะนำให้คุณทำตามวิธีเดิม ๆ ที่ทำกันมา เพราะวิธีจะช่วยให้ Developer ใช้การตัดสินใจที่น้อยลงและไม่ต้องเหนื่อยกับการทำความเข้าใจมากเกินไป

ที่มา:  https://itnext.io/

 

 

รับตำแหน่งงานไอทีใหม่ๆ ด้วยบริการ IT Job Alert

 

อัพเดทบทความจากคนวงในสายไอทีทาง LINE ก่อนใคร
อย่าลืมแอดไลน์ @techstarth เป็นเพื่อนนะคะ

เพิ่มเพื่อน

 

บทความล่าสุด