ศูนย์ข้อผิดพลาด เพราะตัวตรวจตายก่อนจะนับ

ศูนย์ข้อผิดพลาด เพราะตัวตรวจตายก่อนจะนับ

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

เลขศูนย์มีสองความหมาย ความหมายแรกคือ "ตรวจแล้วไม่พบ" ความหมายที่สองคือ "ไม่ได้ตรวจ" บนหน้าจอ สองอย่างนี้เขียนเหมือนกัน

เย็นวันที่ 9 ตุลาคม ห้องหนึ่งของเราได้เลขศูนย์แบบที่สอง และงานชิ้นนั้นถูกรายงานว่าผ่าน ("ห้อง" ของเราคือ AI หนึ่งตัวที่ดูแลงานด้านหนึ่ง)

วิธีตรวจที่ดูสมเหตุสมผล

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

ตัวช่วยเขียนโค้ดของเรารันตามนี้ ได้ 0 ก่อนแก้ และ 0 หลังแก้ ศูนย์เท่ากับศูนย์ จึงรายงานว่าผ่าน

สิ่งที่เกิดขึ้นจริง

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

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

เมื่อรันใหม่โดยให้หน่วยความจำ 16 GB ตัวเลขจริงคือ 3,058

แผนภาพตัวตรวจที่ตายก่อนพิมพ์ แล้วคำสั่งนับบรรทัดได้ศูนย์

วิธีแก้มีสองบรรทัด

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

โปรแกรมบอกเราอยู่แล้วว่ามันตาย เราเพียงไม่ได้ถาม เราถามแต่ว่า "มีกี่บรรทัด"

ไม่ใช่ห้องเดียว

ในวันเดียวกัน อีกห้องหนึ่งเจอปัญหาเดียวกันจากอีกด้าน โมเดลเปิดของห้องนั้นรันตัวตรวจชนิดข้อมูลทั้งคลังโค้ดแล้วหน่วยความจำหมดเหมือนกัน แต่แทนที่จะได้ศูนย์ปลอม มันวนลองใหม่จนชนเพดาน 50 รอบของงาน และชนเพดานแบบนี้ 4 ครั้ง บันทึกของห้องเขียนสาเหตุไว้ตรงๆ ว่า "ไม่มีใครบอกมัน" สุดท้ายห้องต้องรันเองโดยตั้งหน่วยความจำไว้ 24,000 MB และจดบทเรียนว่า ให้ใส่ค่าหน่วยความจำ ไฟล์ตัวเลขฐาน และคำสั่งที่ถูกต้องไว้ในโจทย์ตั้งแต่ครั้งแรก

สองห้องนี้พังต่างกัน ห้องหนึ่งได้คำตอบผิดอย่างเงียบๆ อีกห้องเสียเวลาอย่างเสียงดัง แบบแรกอันตรายกว่ามาก เพราะแบบที่สองอย่างน้อยทุกคนรู้ว่ามีปัญหา

ทำไมเรายังไม่ถือว่าปิด

คืนนั้นกติกาเรื่อง "ศูนย์ปลอมจากตัวตรวจชนิดข้อมูล" ถูกเพิ่มเข้าคู่มือตรวจงานที่ห้องต่างๆ ใช้ร่วมกัน พร้อมกติกาอื่นอีกสามข้อ

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

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

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

สิ่งที่เรายังไม่รู้

ข้อคิดที่เอาไปใช้ได้

  1. ถ้าได้ศูนย์ ให้ถามก่อนว่าตัวตรวจทำงานจบจริงไหม ดูรหัสจบการทำงาน ไม่ใช่ดูแค่ผลลัพธ์
  2. ตัวเลขฐานควรไม่เป็นศูนย์ ถ้ารู้อยู่ว่าโค้ดเก่ามีข้อผิดพลาดหลายพันรายการ การได้ศูนย์ตอน "ก่อนแก้" คือสัญญาณเตือนในตัว ควรจดตัวเลขฐานไว้ในไฟล์แล้วเทียบกับมัน
  3. การเทียบสองค่าที่พังเหมือนกันจะได้ผลว่าเท่ากันเสมอ "ก่อนเท่ากับหลัง" พิสูจน์อะไรไม่ได้ ถ้าทั้งสองค่ามาจากเครื่องมือที่ตายแบบเดียวกัน
  4. บอกข้อจำกัดของเครื่องมือไว้ในโจทย์ ไม่ว่าผู้รับงานจะเป็นคนหรือโมเดล
  5. กติกาที่ต้องอาศัยการจำ ยังไม่ใช่การแก้ จนกว่าจะมีอะไรสักอย่างที่ตกเมื่อเราลืม

คำถามที่พบบ่อย

ทำไมตัวตรวจชนิดข้อมูลรายงานข้อผิดพลาด 0 ทั้งที่มีจริงหลายพันจุด

ค่าตั้งต้นของมันให้ใช้ได้ราว 4 GB ซึ่งไม่พอ โปรแกรมจึงตายกลางทางทั้งสองครั้ง ก่อนจะพิมพ์ข้อผิดพลาดออกมาแม้แต่บรรทัดเดียว

จะแยกผลศูนย์จริงกับศูนย์ปลอมจากตัวตรวจได้อย่างไร

รหัส 2 แปลว่าตรวจเสร็จและพบข้อผิดพลาด รหัส 134 แปลว่าโปรแกรมตาย