Parse, don’t validate

Share
Parse, don’t validate

ผมได้รู้จัก Principle นี้ตอนศึกษาเรื่อง Type System และเร็วๆ นี้เพิ่งเห็นน้องในทีมนำ Library ที่ใช้แนวคิดนี้มาใช้ แต่พอเราไม่เข้าใจหลักการเบื้องหลัง (Core Principle) การนำไปใช้งานเลยติดขัดและเกิดปัญหาตามมา เลยคิดว่าน่าจะหยิบเรื่องนี้มาเล่าให้ฟังกันสักหน่อยครับ

ทำไมการ "Validate" (ตรวจสอบ) ถึงไม่เพียงพอ?

ปกติเวลาเราพูดถึงการเช็คข้อมูล (Validation) เรามักจะนึกถึงฟังก์ชันที่รับ Data เข้ามา เช็คว่าถูกต้องตามเงื่อนไขไหม ถ้าไม่ถูกก็โยน Error แต่ถ้าถูกก็ปล่อยผ่านไป (คืนค่าเดิม หรือคืนค่า Boolean กลับไป)

ลองนึกภาพโค้ดที่เราเห็นกันจนชินตา:

"เรามักจะเช็ค Input ก่อนใช้งานเสมอ เช่น เช็คว่า List ไม่ว่างนะ, เช็คว่า String เป็น Email นะ... แต่เคยสงสัยไหมว่า พอส่งตัวแปรนี้ต่อไปยังฟังก์ชันถัดไป ทำไมเรายังต้องมานั่งเขียน if เช็คเรื่องเดิมซ้ำอีก? หรือถ้าลืมเช็ค โปรแกรมก็พังคามือเป็น Runtime Error ทันที"

ปัญหาคือ: การ Validate แบบนี้ไม่ได้ทิ้ง "หลักฐาน" อะไรไว้ในระบบ Type เลยว่าข้อมูลนี้ปลอดภัยแล้ว ความรู้ (Knowledge) ที่เราได้มาจากการเช็คหายวับไปทันทีที่จบฟังก์ชันนั้น นี่คือสัญญาณว่าเรากำลังใช้ Type System ได้ไม่คุ้มค่าครับ

พลังของการ "Parse": เปลี่ยนจากด่านตรวจ เป็นโรงงานแปรรูป

การเปลี่ยนจาก Validate มาเป็น Parse คือการเปลี่ยนวิธีคิด แทนที่จะแค่ "ตรวจสอบ" เฉยๆ เราจะ "แปลง" ข้อมูลจากโครงสร้างที่ หลวมและไม่แน่นอน (Less-structured) ให้กลายเป็นโครงสร้างที่ ชัดเจนและเข้มงวด (More-structured)

ลองดูตัวอย่างการใช้ NonEmptyList แทน List ทั่วไป:

  • สร้างความมั่นใจผ่านโครงสร้าง: แทนที่จะรับ List ปกติ [a] ที่อาจจะว่างก็ได้ เราเปลี่ยนมารับ NonEmptyList a ซึ่งโครงสร้างของมันบังคับว่าต้องมีสมาชิกอย่างน้อยหนึ่งตัวเสมอ
  • ลดภาระของฟังก์ชัน: เมื่อ Type บังคับมาแล้ว ฟังก์ชัน head (ดึงตัวแรก) ก็สามารถคืนค่าได้ทันทีโดยไม่ต้องลุ้นว่าเป็น null หรือเปล่า
  • พิสูจน์ครั้งเดียวจบ: เราย้ายการตรวจสอบไปไว้ที่ "ขอบหน้าต่าง" ของโปรแกรม (Boundary) เพียงครั้งเดียวเพื่อแปลง List ปกติให้เป็น NonEmptyList

หลังจากจุดนั้นเป็นต้นไป ระบบ Type จะเป็นคนช่วย "พิสูจน์" (Proof) ให้เราเอง ว่าข้อมูลนี้ถูกต้องแน่นอนตลอดทั้งโปรแกรม โดยที่เราไม่ต้องเขียน if เช็คซ้ำอีกเลย

Validation vs. Parsing: มุมมองของ "ข้อมูล" และ "ความรู้"

  • Validation: คือการเช็คว่าข้อมูล "เลวร้าย" ไหม ถ้าไม่ ก็ปล่อยผ่านไป (คืนค่าเดิม)
    • จุดอ่อน: ความรู้ที่ว่า "ข้อมูลนี้ถูกต้องแล้ว" จะหายไปทันทีที่จบฟังก์ชันนั้น
  • Parsing: คือการนำข้อมูลดิบ (Less structured) มาแปลงเป็นข้อมูลที่มีโครงสร้างชัดเจนขึ้น (More structured)
    • จุดแข็ง: ข้อมูลใหม่ที่ได้มาคือ "หลักฐาน" ในเชิง Type ว่าข้อมูลนี้ถูกต้องแน่นอน 100%

เริ่มต้นเปลี่ยนวิธีเขียนโค้ดอย่างไรดี?

  1. เน้นที่ Data Types: เลือกใช้โครงสร้างข้อมูลที่ทำให้ "สถานะที่ผิดกฎหมายไม่สามารถเกิดขึ้นได้" (Make illegal states unrepresentable) เช่น ใช้ Map แทน List ของ Key-Value เพื่อป้องกัน Key ซ้ำ
  2. ผลักภาระการพิสูจน์ขึ้นไปให้สูงที่สุด: พยายาม Parse ข้อมูลให้เป็นรูปแบบที่แม่นยำที่สุดตั้งแต่จุดที่รับข้อมูลเข้ามา (เช่น จาก API หรือ User Input) ก่อนจะส่งต่อเข้าไปในโปรแกรม
  3. เขียนฟังก์ชันตามข้อมูลที่ "อยากให้เป็น": อย่าเขียนโค้ดตามรูปแบบข้อมูลที่ได้รับมา แต่ให้เขียนตามรูปแบบที่คุณต้องการ แล้วค่อยหาวิธีแปลงข้อมูล (Bridge the gap) มาสู่จุดนั้น
  4. ให้ระวังฟังก์ชันที่คืนค่าว่าง (Unit): ฟังก์ชันที่รับค่าไปตรวจสอบแล้วไม่คืนอะไรกลับมา นอกจาก Error เมื่อผิดพลาด มักจะเป็นจุดที่ข้อมูล "ความรู้" รั่วไหล เพราะมันไม่ได้ส่งต่อ Type ที่ดีกว่าเดิมออกมา

ตัวอย่างเปรียบเทียบใน TypeScript

แบบเดิม (Validation): เสี่ยงและซ้ำซ้อน

function processList(items: string[]) {
  if (items.length === 0) throw new Error("List is empty");
  
  // แม้จะเช็คแล้ว แต่ 'items' ก็ยังเป็น string[] ทั่วไป 
  // ฟังก์ชันอื่นที่รับ items ไป ก็ต้องมาเช็คซ้ำอีกอยู่ดี
  const head = items[0]; 
}

แบบใหม่ (Parsing): ปลอดภัยและชัดเจน

// สร้าง Type ใหม่ที่การันตีว่า "ต้องมีข้อมูล"
type NonEmptyList<T> = [T, ...T[]];

function parseNonEmpty<T>(items: T[]): NonEmptyList<T> | null {
  return items.length > 0 ? (items as NonEmptyList<T>) : null;
}

function processList(items: NonEmptyList<string>) {
  // ไม่ต้องเช็ค if (items.length === 0) อีกต่อไป!
  // เพราะ Type บังคับมาแล้วว่าส่ง List ว่างมาไม่ได้
  const head = items[0]; 
}
ปล. อย่าพึ่งด่าผมนะ ผมยังไม่เก่ง TypeScript ขนาดที่จะทำให้โค้ดใน TypeScript มันฉลาดพอจะเป็น Proof ในระดับ Type บน Compile Time ได้ ผมเลือกมาเพราะคิดว่าคนส่วนใหญ่น่าจะพออ่านเข้าใจ แต่ผมเชื่อว่ามันทำได้นะ

ประโยชน์ที่ได้

  1. Code สะอาดขึ้น: ลด if-else เช็ค Error ยิบย่อยใน Business Logic
  2. เป็นเอกสารในตัว (Self-documenting): แค่อ่าน Signature ของฟังก์ชันก็รู้แล้วว่ามันต้องการข้อมูลแบบไหน
  3. ความปลอดภัยสูง: ลดความเสี่ยงเรื่อง Security เช่น Injection ต่างๆ เพราะข้อมูลถูก Parse ให้เป๊ะตั้งแต่ปากทางเข้า (Boundary)
  4. ลดการเกิด Shotgun Parsing: การทำสิ่งนี้จะทำให้โปรแกรมถูกแบ่งเป็น Parsing phase และ Execution Phase ซึ่งทำให้โค้ดสำหรับตรวจสอบข้อมูลจะไม่ถูกถูกกระจายไปทั่วทุกส่วนของโปรแกรมปนกับ Business Logic (ซึ่งเป็นสาเหตุหลักของ Bug ที่หาตัวยาก)

การเปลี่ยนจาก "ตรวจสอบ" มาเป็น "แปลงข้อมูล" อาจจะดูเหมือนต้องเขียนโค้ดเพิ่มขึ้นในตอนแรก แต่มันจะช่วยให้คุณและทีมทำงานง่ายขึ้นในระยะยาว เพราะเราให้ Compiler ช่วยทำงานแทนเรานั่นเองครับ :)

Read more

เริ่มต้นกับ Functional Programming

เริ่มต้นกับ Functional Programming

ก่อนหน้านี้ผมเจอโพสต์หนึ่งถามว่า สำหรับคนที่เคยใช้แต่ OOP จะเริ่มต้นกับ Functional Programming ควรเริ่มยังไงดี ผมชอบคำตอบที่ตัวเองตอบไป เลยอยากเอามาจดเก็บไว้ ฝันร้ายของโปรแกรมเมอร์ ก่อนจะเข้าไปใน programming แต่ละ paradigm ผมอยากเริ่มต้นที่ปัญหาก่อน หนึ่งในปัญหาที่หลอกหลอนโปรแกรมเมอร์มากที่สุด คื

By Chokchai Phatharamalai
Values ของ Framework ต่าง ๆ

Values ของ Framework ต่าง ๆ

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

By Chokchai Phatharamalai
Accepted Responsibility

Accepted Responsibility

ช่วงนี้ผมกำลังอ่านหนังสือ Extreme Programming Explained ของ Kent Beck แล้วก็ได้เจอหัวข้อหนึ่งที่กระทบใจ คือ principle ที่เรียกว่า Accepted Responsibility Principle คืออะไร? ในหนังสืออธิบายว่า Principle คือหลักการที่ทำให้ Values ของ XP กลายเป็น Practices ได้

By Chokchai Phatharamalai
The Five Ideals

The Five Ideals

จากหนังสือ The Unicorn Project ในนั้นจะมีที่ปรึกษาเก่ง ๆ คนหนึ่งชื่อ Erik เป็นเหมือนเซ็นเซหาตัวยาก เดี๋ยวก็มา เดี๋ยวก็ไป ถ้าโชคดีก็จะเจอเค้าว่าง แล้วเค้าก็จะสอนอะไรที่สำคัญ ๆ แต่ฟังดูล้ำ ๆ และลึกซึ้ง

By Chokchai Phatharamalai