productize.life
TH EN
AI · Production Skills

โค้ดที่พอร์ตมา เทสต์ผ่านหมด แต่ยังผิดได้

ตอนย้ายโปรแกรมคำนวณงบการเงินไปเขียนใหม่ เทสต์ที่เขียนเองผ่านหมด แต่มันพิสูจน์อะไรไม่ได้ เพราะคนเขียนเทสต์กับคนเขียนโค้ดคิดด้วยสมมติฐานชุดเดียวกัน วิธีที่พิสูจน์ได้จริงคือ characterization testing เอาของเดิมที่รันอยู่มาเป็นคำเฉลย

Yim· เขียนด้วยกันกับ Dobby (AI Oracle)/1 ก.ค. 2026

ช่วงที่ผ่านมาเราย้ายโปรแกรมคำนวณงบการเงินตัวหนึ่ง จากโค้ดเดิมไปเขียนใหม่อีกภาษา เป็นโปรแกรมที่รับผังบัญชีกับยอดทดลองเข้าไป แล้วคำนวณออกมาเป็นงบการเงิน พอเขียนของใหม่เสร็จก็เขียนเทสต์คุมไว้ รันแล้วผ่านหมดทุกตัว ไม่ตกสักตัว

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

เส้นแบ่งตรงนี้สำคัญมากตอนพอร์ตโค้ด เพราะงานพอร์ตต่างจากเขียนของใหม่ ตรงที่รู้อยู่แล้วว่าผลที่ถูกต้องคืออะไร นั่นคือของเดิมที่รันจริงมาก่อน วิธีที่เรียกว่า characterization testing ใช้ประโยชน์จากตรงนี้พอดี บทความนี้เล่าเป็นขั้น เริ่มจากมันคืออะไร ต่อด้วยวิธีพิสูจน์การพอร์ตด้วยการรันคู่ แล้วปิดที่กฎข้อหนึ่งที่ขัดสามัญสำนึก คือเจอบั๊กในโค้ดเดิม ให้ทำให้เหมือนไว้ก่อน อย่าเพิ่งแก้

ช่วงที่ 1characterization testing คืออะไร

characterization testing แปลตรงตัวคือการเทสต์เพื่อ "บันทึกลักษณะ" ของโค้ด แทนที่จะเริ่มจากถามว่าโค้ดควรทำอะไร มันเริ่มจากถามว่าโค้ดทำอะไรอยู่จริงตอนนี้ แล้วล็อกคำตอบนั้นไว้เป็นคำเฉลย จากนั้นทุกครั้งที่แก้โค้ด ก็เทียบกับคำเฉลยว่ายังเหมือนเดิมไหม เป็นชื่อที่ Michael Feathers ตั้งไว้ในหนังสือ Working Effectively with Legacy Code สำหรับงานรื้อโค้ดเก่าที่ยังไม่มีเทสต์

เทคนิคตระกูลนี้มีหลายชื่อ snapshot testing ที่คนทำหน้าเว็บคุ้น approval testing และ golden master ทั้งหมดหลักเดียวกัน คือเก็บผลลัพธ์ชุดหนึ่งไว้ตายตัว แล้วใช้มันเป็นตัวตัดสินของครั้งต่อไป ต่างกันแค่บริบทและเครื่องมือ

ทำไมมันเหมาะกับการพอร์ตเป็นพิเศษ

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

ย้อนกลับไปที่ปัญหาตอนต้น เทสต์ที่เขียนเองมีจุดบอดตรงนี้พอดี ถ้าคนเขียนเทสต์กับคนเขียนโค้ดเข้าใจโจทย์มาแบบเดียวกัน ความเข้าใจที่ผิดจะติดไปทั้งสองฝั่งเท่ากัน เทสต์เลยยืนยันได้แค่ว่าโค้ดตรงกับความเข้าใจของเรา ไม่ได้ยืนยันว่าความเข้าใจนั้นถูก characterization testing ข้ามจุดบอดนี้ด้วยการเอาของเดิมมาเป็นคำเฉลย แทนความเข้าใจของเราเอง

ช่วงที่ 2พิสูจน์การพอร์ตด้วยการรันคู่ แล้วล็อกผลไว้เป็นคำเฉลย

วิธีที่พิสูจน์ได้แน่นที่สุดมีสามจังหวะ

  1. รันคู่ เอาของเดิมกับของใหม่มารันด้วยชุดข้อมูลเดียวกัน เก็บผลทั้งสองไว้
  2. เทียบทีละบรรทัดแบบ byte-diff ไม่ใช่ดูรวม ๆ ว่าใกล้กัน ตัวเลขต้องตรงทั้งตำแหน่งและค่า ถ้าต่างแม้บรรทัดเดียว แปลว่าพอร์ตยังไม่ตรง ต้องไล่ให้รู้ว่าทำไม
  3. ล็อกไว้เป็นคำเฉลย พอผลตรงกันหมดแล้ว เก็บผลของเครื่องต้นทางไว้เป็นไฟล์คำเฉลยตายตัว ไฟล์แบบนี้ในวงการเรียกว่า golden จากนั้นเทสต์ก็ไม่ต้องมีเครื่องเดิมอยู่ด้วยแล้ว แค่รันของใหม่ แล้วเทียบกับไฟล์คำเฉลยนี้

ยกจากงานจริง ตอนพอร์ตโปรแกรมคำนวณงบ เราเก็บผลของเครื่องต้นทางไว้เป็นไฟล์คำเฉลย (golden) แล้วตั้งชุดตรวจความตรงไว้ร้อยกว่าจุด ครอบทั้งงบแสดงฐานะการเงิน งบกำไรขาดทุน และงบกระแสเงินสด ทุกครั้งที่แก้โค้ดของใหม่ ชุดตรวจนี้จะเตือนทันทีถ้าผลเพี้ยนจากคำเฉลยแม้แต่บรรทัดเดียว โปรแกรมคำนวณจึงล็อกความถูกไว้ได้ โดยไม่ต้องแบกโค้ดเดิมไปทั้งก้อน

เครื่องเดิม ที่รันจริงอยู่ เครื่องใหม่ ที่พอร์ตมา ข้อมูลชุดเดียวกัน เทียบ byte-diff ทีละบรรทัด ตรงกันหมด เก็บเป็นคำเฉลย ต่างแม้บรรทัดเดียว พอร์ตพลาด หรือเจอบั๊กเดิม
รันเครื่องเดิมกับเครื่องใหม่ด้วยข้อมูลชุดเดียวกัน แล้วเทียบผลทีละบรรทัด ตรงกันหมดค่อยเก็บไว้เป็นคำเฉลย (golden) ต่างแม้บรรทัดเดียว แปลว่ามีจุดที่ต้องไล่หา

อย่าเชื่อข้อมูลจำลองที่ปั้นเอง

มีจุดที่คนมองข้ามบ่อยตอนเขียนเทสต์ อย่าเชื่อ mock หรือข้อมูลจำลองที่เราปั้นขึ้นมาเทสต์เอง ให้ยึดรูปร่างข้อมูลจริงจากต้นทาง ในงานนี้ ข้อมูลจำลองที่เขียนไว้ตั้งชื่อช่องข้อมูลไว้ชุดหนึ่ง แต่โครงสร้างข้อมูลจริงที่เครื่องต้นทางคืนมาใช้ชื่อคนละชุด ถ้าเชื่อข้อมูลจำลอง เทสต์จะผ่าน แต่พอต่อของจริงจะพัง หลักสั้น ๆ คือ ข้อมูลจำลองโกหกได้ โครงสร้างข้อมูลจริงไม่โกหก

ช่วงที่ 3เจอบั๊กในโค้ดเดิม ทำให้เหมือนไว้ก่อน อย่าเพิ่งแก้

ระหว่างเทียบผล characterization testing มักเจอของแถมที่ไม่ได้ตั้งใจหา นั่นคือบั๊กในโค้ดเดิม เพราะพอต้องเทียบทุกบรรทัด เราเลยได้อ่านพฤติกรรมของเครื่องต้นทางละเอียดกว่าตอนใช้งานปกติ

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

สัญชาตญาณแรกคืออยากแก้มันเดี๋ยวนั้น แต่ในงานพอร์ต การแก้บั๊กในโค้ดใหม่กลับเป็นสิ่งที่ไม่ควรทำ เพราะเป้าหมายของการพอร์ตคือให้ของใหม่ทำงานเหมือนของเดิมเป๊ะ ทั้งส่วนที่ถูกและส่วนที่ผิด ถ้าเราแอบแก้บั๊กในโค้ดใหม่ ผลจะไม่ตรงกับคำเฉลยของเดิมทันที แล้วเราจะแยกไม่ออกว่าที่ผลต่าง เป็นเพราะพอร์ตพลาด หรือเพราะเราตั้งใจแก้

สิ่งที่ทำคือ ทำให้ของใหม่ผิดตามของเดิมให้ตรงเป๊ะ แล้วเขียนคอมเมนต์กำกับไว้ว่านี่คือบั๊กที่รู้ตัว พร้อมส่งเรื่องกลับไปให้เจ้าของโค้ดต้นทางแก้ที่ต้นทาง พอต้นทางแก้เมื่อไหร่ ความต่างจะโผล่มาให้เห็นชัด ๆ ให้เราตามแก้ของใหม่ได้ถูกจุด

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

ช่วงที่ 4เอาไปใช้กับงานจริง

ใช้กับงานแบบไหนได้บ้าง

กฎข้อเดียวที่ต้องจำ

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

เริ่มยังไงดี

ไม่ต้องวางระบบใหญ่ตั้งแต่แรก ลองกับโค้ดก้อนเดียวก่อน

  1. เลือกโค้ดก้อนที่กำลังจะพอร์ตหรือรื้อ ที่ยังมีของเดิมรันได้อยู่
  2. เตรียมชุดข้อมูลตัวอย่างที่ครอบคลุมกรณีสำคัญ รันผ่านของเดิม เก็บผลไว้เป็นคำเฉลย (golden)
  3. รันของใหม่ด้วยชุดเดียวกัน เทียบกับคำเฉลยทีละบรรทัด
  4. ต่างตรงไหน ไล่ให้รู้ว่าเป็นเพราะพอร์ตพลาด หรือเจอบั๊กของเดิม ถ้าเป็นบั๊กเดิม ทำให้เหมือนไว้ก่อนพร้อมคอมเมนต์
  5. พอตรงกันหมด ล็อกคำเฉลยไว้เป็นเทสต์ถาวร ของใหม่แก้เมื่อไหร่ มันจะเตือนทันทีถ้าผลเพี้ยน
อ่านต่อในซีรีส์ production-grade
ที่มาและอ้างอิง

บทความนี้เป็นหนึ่งชั้นใน สถาปัตยกรรม AI agent ระดับ production ทั้ง 7 ชั้น

ติดตาม

รับบทความใหม่และของฟรีก่อนใคร

ทิ้งอีเมลไว้ บทความใหม่และของฟรีเป็นครั้งคราวจะส่งไปให้ ไม่สแปม

ใช้อีเมลเพื่อส่งอัปเดตเท่านั้น

ความคิดเห็น

ร่วมพูดคุย

แบ่งปันความคิดเห็นได้เลย

ชื่อจะแสดงต่อสาธารณะ อีเมลเก็บเป็นความลับ ไม่แสดงที่ไหน

กำลังโหลดความคิดเห็น…