ทำไมโค้ดที่รันผ่านแล้ว ยังพังได้ใน Monorepo
สมมติว่าคุณกำลังทำโปรเจกต์ใหญ่ที่มีหลายส่วนงานรวมอยู่ในที่เดียว หรือที่เรียกว่า Monorepo (ที่เก็บโค้ดหลายๆ โปรเจกต์ไว้ในโฟลเดอร์ใหญ่ที่เดียว) คุณอาจจะคิดว่าถ้าโปรแกรมส่วนที่ผลิตโค้ดทำงานสำเร็จแล้ว โปรแกรมส่วนที่เหลือก็น่าจะทำงานต่อได้ปกติ แต่ในความเป็นจริงมันไม่ได้เป็นแบบนั้นเสมอไปครับ
ปัญหาที่พบบ่อยคือ Artifact Lineage (ลำดับการสืบทอดของผลผลิต) ซึ่งก็คือการที่โค้ดส่วนหนึ่งถูกสร้างขึ้นมาเพื่อให้อีกส่วนเอาไปใช้ต่อ ถ้าตัวผลิตทำงานเสร็จแต่ตัวใช้ไม่ได้รับข้อมูลที่ถูกต้อง ระบบทั้งหมดก็พังได้ การตรวจสอบแค่ว่า "ตัวผลิตทำงานผ่านไหม" จึงไม่เพียงพอสำหรับการการันตีคุณภาพ
เปรียบเทียบง่ายๆ เหมือนการทำอาหารในครัวใหญ่ครับ ถ้าคนเตรียมวัตถุดิบหั่นผักเสร็จแล้ววางไว้ แต่คนปรุงอาหารหาผักไม่เจอหรือผักหั่นมาผิดขนาด คนปรุงก็ทำอาหารต่อไม่ได้ ถึงแม้คนเตรียมจะบอกว่า "ผมหั่นเสร็จแล้วนะ" แต่งานโดยรวมก็ยังไม่สำเร็จอยู่ดีครับ
รู้จักกับ Ota และการจัดการงานอัตโนมัติ
Ota คือเครื่องมือที่ช่วยเราจัดการ Automation (การทำงานอัตโนมัติ) ในโปรเจกต์พัฒนาซอฟต์แวร์ มันช่วยให้เรามั่นใจว่าทุกขั้นตอนการทำงานมีความสัมพันธ์กันอย่างถูกต้อง Ota ไม่ได้ดูแค่ว่างานจบไหม แต่มันดูไปถึงว่างานนั้นส่งผลกระทบต่อใครบ้างในระบบ
ในการพัฒนาซอฟต์แวร์สมัยใหม่ เรามักใช้ Dependency Hydration (กระบวนการดึงข้อมูลและติดตั้งส่วนประกอบที่โปรเจกต์จำเป็นต้องใช้) ซึ่งถ้าเราจัดการไม่ดี เราอาจจะดึงของมาไม่ครบหรือผิดเวอร์ชัน การใช้ Ota ช่วยให้เราคุมเส้นทางนี้ได้แม่นยำกว่าการสั่งรันคำสั่งแบบสุ่มสี่สุ่มห้า
มือใหม่มักพลาดตรงที่ชอบสั่งรันคำสั่งทีเดียวจบแบบมักง่าย ทำให้ไม่รู้ว่าจุดไหนที่พังจริง การใช้เครื่องมืออย่าง Ota จะทำให้เราเห็นภาพชัดขึ้นว่า โค้ดส่วนไหนผลิตอะไร และใครคือคนเอาไปใช้ต่อครับ
การเชื่อมโยงข้อมูลระหว่างผู้ผลิตและผู้ใช้งาน
ในโปรเจกต์ EventCatalog จะมีเครื่องมือชื่อ Langium (เครื่องมือสร้างภาษาโปรแกรม) ทำหน้าที่ผลิตไฟล์โค้ดต่างๆ ออกมา เช่น ไฟล์โครงสร้างข้อมูลหรือไวยากรณ์ภาษา ซึ่งไฟล์เหล่านี้จะถูกนำไปใช้ต่อโดย VS Code Extension (ปลั๊กอินเสริมสำหรับโปรแกรมเขียนโค้ด) เพื่อช่วยให้โปรแกรมเมอร์เขียนโค้ดได้ง่ายขึ้น
เราต้องกำหนดสัญญาหรือ Contract (ข้อตกลงเรื่องรูปแบบข้อมูล) ให้ชัดเจนว่าไฟล์ที่ผลิตออกมาต้องวางไว้ที่ไหนและมีชื่อว่าอะไร เพื่อให้ตัวใช้งานหาเจอ การทำแบบนี้ทำให้เราไม่ต้องเดาว่าไฟล์อยู่ที่ไหนหรือต้องใช้เวอร์ชันไหนในการทำงาน
ลองดูตัวอย่างการประกาศความสัมพันธ์ของไฟล์ใน Ota ครับ:
artifacts:
language-server-ast: # ชื่อผลผลิตที่สร้างขึ้น
kind: generated_source # ประเภทคือโค้ดที่ถูกสร้างโดยอัตโนมัติ
producer: language-server:generate # ใครเป็นคนสร้าง
paths:
- packages/language-server/src/generated/ast.ts # ไฟล์ที่สร้างขึ้น
inputs:
- packages/language-server/src/ec.langium # ไฟล์ต้นทางที่ใช้สร้าง
ในโค้ดนี้ เราบอก Ota ว่า language-server-ast คือผลผลิตที่สร้างจากไฟล์ต้นทาง ec.langium โดยมีผู้ผลิตคือ language-server:generate ถ้าไฟล์ต้นทางเปลี่ยน ผลผลิตก็จะถูกสร้างใหม่ทันที
ผลลัพธ์คือ Ota จะรู้ทันทีว่าถ้าไฟล์ ec.langium เปลี่ยนแปลง มันต้องสั่งให้ language-server:generate ทำงานใหม่ก่อนที่จะให้ส่วนอื่นมาดึงไฟล์ไปใช้ครับ
ทำไมต้องทดสอบแบบเรียลไทม์
หลายคนชอบทดสอบแค่ส่วนประกอบย่อยๆ แต่การทดสอบที่ทรงพลังที่สุดคือการรัน Consumer Closure (ขอบเขตการทำงานทั้งหมดของผู้ใช้งาน) คือการจำลองการทำงานตั้งแต่ต้นจนจบจริงๆ ว่าเมื่อประกอบกันแล้วโปรแกรมทำงานได้ปกติไหม
การรันแบบนี้จะช่วยให้เราเห็นบั๊กที่ซ่อนอยู่ เช่น ไฟล์ที่สร้างขึ้นมาอาจจะใช้งานได้กับ Windows แต่พังบน Linux หรือ macOS การทดสอบบนหลายระบบปฏิบัติการจึงสำคัญมาก เพื่อให้แน่ใจว่าผลผลิตของเราใช้งานได้จริงในทุกสภาพแวดล้อม
มือใหม่มักจะละเลยการทดสอบแบบนี้เพราะมันใช้เวลานาน แต่ถ้าคุณอยากเป็นโปรแกรมเมอร์ที่เก่ง คุณต้องเริ่มสร้างนิสัยการทำ Verification Workflow (ขั้นตอนการตรวจสอบความถูกต้อง) ให้เป็นส่วนหนึ่งของงานประจำครับ
ข้อจำกัดที่เราต้องยอมรับ
แม้เราจะทำระบบตรวจสอบได้ดีแค่ไหน แต่มันก็ยังมี Boundaries (ขอบเขตหรือข้อจำกัด) เสมอ เช่น การทดสอบใน Ota อาจจะตรวจสอบได้ว่าไฟล์ถูกสร้างขึ้นและใช้งานได้ แต่ไม่ได้การันตีว่าตรรกะภายในโปรแกรมนั้นถูกต้องสมบูรณ์ 100% หรือไม่
เราต้องระวังไม่ให้ความมั่นใจจากการรันผ่าน (Green build) ทำให้เราชะล่าใจ การที่ระบบบอกว่าผ่าน ไม่ได้แปลว่าไม่มีบั๊กแฝงอยู่ การทดสอบเป็นเพียงการเพิ่มความมั่นใจในจุดที่เรากำหนดไว้เท่านั้น ไม่ใช่การรับประกันความสำเร็จในทุกมิติ
คำแนะนำสำหรับมือใหม่คือ อย่าพยายามทำระบบทดสอบที่ครอบคลุมทุกอย่างตั้งแต่เริ่มต้น ให้เริ่มจากจุดที่พังบ่อยที่สุดก่อน แล้วค่อยๆ ขยายขอบเขตออกไปเมื่อเราเข้าใจระบบดีขึ้นครับ
สรุป: การนำไปใช้จริงสำหรับมือใหม่
สรุปสั้นๆ คือ การทำโปรเจกต์ใหญ่คุณต้องคิดถึง Lineage (สายการสืบทอด) ของไฟล์ให้ดี อย่ามองว่าไฟล์ที่สร้างเสร็จแล้วคือจบงาน แต่ต้องมองว่าใครจะมาหยิบไฟล์นั้นไปใช้ และเขาต้องการอะไรจากไฟล์นั้นบ้าง
ถ้าคุณกำลังฝึกทำโปรเจกต์ใน GitHub ให้ลองหัดเขียน GitHub Action (ระบบสั่งงานอัตโนมัติเมื่อมีการอัปเดตโค้ด) เพื่อทดสอบว่าโค้ดของคุณสามารถติดตั้งและรันได้จริงในทุกครั้งที่มีการเปลี่ยนโค้ด นี่คือทักษะที่บริษัทชั้นนำมองหาในตัวโปรแกรมเมอร์ครับ
ตัวอย่างการนำไปใช้จริง: ถ้าคุณทำเว็บแอปที่ต้องสร้างไฟล์ config.json จากข้อมูลในฐานข้อมูล อย่าแค่เขียนโค้ดสร้างไฟล์จบไป แต่ให้เพิ่มการทดสอบเล็กๆ ว่าไฟล์ config.json ที่สร้างขึ้นมานั้น มีคีย์ข้อมูลที่จำเป็นครบถ้วนและโปรแกรมส่วนหน้าเว็บอ่านค่าได้ถูกต้องจริงๆ เพียงเท่านี้คุณก็ก้าวข้ามโปรแกรมเมอร์มือสมัครเล่นไปอีกขั้นแล้วครับ
ที่มา: Pressure-testing Ota on EventCatalog: generated artifact lineage across sibling consumers — DEV Community