จากบทความในโค้ดทัวร์ ตอน 1 ทำการอธิบาย code ที่เขียนด้วยภาษา Go
มีรูปแบบหรือ pattern ที่น่าสนใจหลายตัว
หนึ่งในนั้นคือ NewEntry(url string)
[gist id="859ae178eb1a6e1f7636ca934e4a98e6" file="1.go"]
ทำให้นึกถึงเรื่องของ A Theory of modern Go
ซึ่งอธิบายถึงเกี่ยวกับ Global state/variable
จึงนำมาสรุปไว้นิดหน่อย
ในภาษา Go นั้นพยายามจะไม่มี magic ในตัวภาษา
เนื่องจาก magic เป็นสิ่งที่ไม่ดี ส่งผลเสียเยอะ แต่ก็มีข้อดีนะ เพื่อทำให้ code อ่านเข้าใจได้ง่าย เพื่อทำให้ code ดูแลรักษาได้ง่าย แต่โชคไม่ดีเท่าไร ที่ Go developer มักจะสร้าง magic ขึ้นมาใช้เอง ยกตัวอย่างเช่น Global state/variable หรือ variable ในระดับ package !! ซึ่งทำให้ code อ่านและดูแลรักษายากมาก ๆคำถาม ใครทำแบบนี้อยู่บ้าง ?จาก tweet ของ Dave Cheney พูดถึง Modern Go ไว้ว่า
- No side effect imports
- No package level variables
มาดูแนวทางกันที่อธิบายไว้ในบทความ
สร้าง function NewXxxx() ขึ้นมาใช้งาน ใช้สำหรับการสร้าง instance ซึ่งทำหน้าที่เหมือนกับ constructor นั่นเอง ดังนั้นผู้เรียกใช้ก็ทำการสร้าง instance ผ่านทาง function นี้ ที่สำคัญต้องสร้างข้อตกลงร่วมกันภายในทีมด้วย ว่าต้องไม่สร้าง instance แบบตรง ๆ เหมือนเดิม ยังไม่พอนะ ถ้าใน NewXxx() มี dependency ต่าง ๆ ด้วย [gist id="859ae178eb1a6e1f7636ca934e4a98e6" file="2.go"] คำอธิบาย จะเห็นได้ว่ามีการ function จาก package database/sql.Conn, logger และ pool เป็นการทำงานภายในของ function นี้ เรียกว่าคือ dependency ซึ่งทำให้เกิดผลกระทบตามมา ทั้ง dbconn, logger และ pool ต่างก็เป็น variable ในระดับ package !! นั่นหมายความว่า ผู้เรียกใช้งานและเปลี่ยนค่าต่าง ๆ ได้เลย ทำให้ไม่สามารถคาดการได้เลยว่าจะเกิดอะไรขึ้นบ้าง ? database จะล่มไหม ? network จะช้าไหม ? ดึงข้อมูลจาก database ถูกหรือไม่ ? จะมี request ใน pool ไหม ?ดังนั้นสิ่งที่ควรทำคือ ทำการส่งค่า dependency ต่าง ๆ ผ่าน parameter เข้ามาได้เลย
เรามักเรียกวิธีการนี้ว่า Dependency Injection นั่นเอง ทำให้ผู้ใช้งานอ่านเข้าใจได้ทันทีว่า ถ้าต้องการให้ทำงานอย่างถูกต้องแล้ว ต้องทำการส่ง parameter อะไรเข้ามาบ้าง เขียนได้ดังนี้ [gist id="859ae178eb1a6e1f7636ca934e4a98e6" file="3.go"] มันดูดีขึ้นนะ แถมไม่มีตัวแปรระดับ package อีกแต่ถ้าต้องการให้ package นี้เป็น public API สามารถทำให้ดีกว่านี้ได้อีก
นั่นคือการสร้าง interface ขึ้นมา เพื่อไม่ให้เกิดการผูกมัดกันเกินไป แถมยังช่วยให้ทดสอบด้วยการ mock dependency ต่าง ๆ ง่ายขึ้นอีกด้วย เขียนได้ดังนี้ [gist id="859ae178eb1a6e1f7636ca934e4a98e6" file="4.go"]สังเกตไหมว่า
เมื่อเหล่า constructor และ function ต่าง ๆ มีหน้าที่การทำงานและขอบเขตที่ชัดเจน กำหนด dependency ต่าง ๆ อย่างชัดเจนแล้ว เราไม่จำเป็นต้องมี global variable หรือ variable ในระดับ package อีกต่อไป เป็นการลด magic ต่าง ๆ ลงไป ผลที่ตามมาคือ การ refactor ที่ง่ายขึ้น การดูแลรักษา code ง่ายขึ้น ดูเพิ่มเติมสำหรับการเขียนตามแนวทางนี่จาก Go kitวันนี้ Go developer เขียน code กันอย่างไร ? มัน simple ตามแนวคิดของภาษาหรือเปล่านะ ?