เมื่อสองวันก่อน หลังจากที่ผมปรับแก้ไขโค้ดส่วนแสดงผลเมนูย่อย แล้วทดสอบว่าใช้งานได้ ก็อัพเดทขึ้นใช้งานรวมกับโค้ดส่วนอื่นตามปกติ แต่เมื่อกลับมาทดสอบโปรแกรมอีกครั้ง กลับใช้งานไม่ได้ อาการร้ายแรงถึงขนาดทำให้เบราเซอร์หยุดการทำงาน (crash) ไม่สามารถแม้กระทั่ง debug ดูว่าเกิดปัญหาอะไร
อีกสักพักหนึ่งผู้ใช้เริ่มทะยอยแจ้งปัญหาเข้ามา เมื่อผู้ใช้ทำงานไม่ได้ สถานการณ์จึงกลายเป็นเร่งด่วน คราวนี้สมองวิ่งจี๋พยายามกรอย้อนเหตุการณ์ว่าทำอะไรไปบ้าง ความผิดพลาดควรเกิดขึ้นที่จุดไหน อุปสรรคสำคัญอยู่ที่เบราเซอร์ หยุดการทำงานไปเลย เหมือนโดนโจมตี
ก่อนหน้านั้นเมื่อต้นสัปดาห์เพิ่งอัพเดทโค้ดใหม่เพื่อทดสอบ Google Sign-In ซึ่งเป็นส่วนที่ค่อนข้างซับซ้อน ความสงสัยจึงมุ่งไปที่ส่วนนั้น
ชั่วโมงที่หนึ่งผ่านไป เป็นการหาวิธีทำให้เบราเซอร์ทำงานต่อได้ โดยไล่ปิดโค้ดและการคอนฟิกค่าการทำงานเพื่อหาจุดที่เกิดปัญหา ต้องรีสตาร์ท ล้างแคช เริ่มใหม่ครั้งแล้วครั้งเล่า เมื่อพบว่าไม่หยุดทำงานแล้วก็ลองตั้งค่าคืนกลับมาทีละส่วน เพื่อตีกรอบสาเหตุของปัญหาให้แคบที่สุด
โชคดีโค้ดส่วน Google Sign-In ที่ซับซ้อนที่สุดไม่เกี่ยว ส่วนของ Thrid Party Library ที่เพิ่งปรับเป็นเวอร์ชั่นล่าสุดไม่เกี่ยว ดังนั้นไม่น่าจะมีโค้ดแปลกปลอมมาโจมตี จนพบว่าเป็นโค้ดส่วนเมนูที่เพิ่งแก้ไข ซึ่งไม่น่าเป็นไปได้เพราะ เป็นการแก้ไขเพียงข้อความที่แสดงในเมนู ไม่ได้ทำอะไรซับซ้อนเลย
เมื่อไล่เจอสาเหตุแล้วอยากจะเขกกะโหลกตัวเอง ผมจะเล่าความเบ๊อะบ๊ะให้ฟัง
โค้ดส่วนเมนูของโปรแกรม ปกติแยกเก็บโค้ดไว้เป็นบล็อกย่อย เช่น มีบล๊อกของเมนู A, B, C แยกเป็นสัดส่วนไม่ปนกัน ในการออกแบบครั้งนี้ เมนู A เอาบล็อกของ C มาใช้ด้วย ดังนั้นในโค้ด A จะใช้คำสั่ง include C
เมื่อต้องการแก้ไขโค้ด A สามารถทำได้สองวิธี เข้าไปแก้ตรง ภายในบล็อกที่เก็บโค้ด หรือส่งโค้ดนั้นออกมาที่โปรแกรม code editor ภายนอก ที่อ่านโค้ดได้ง่ายกว่า เสร็จแล้วค่อยก๊อปปี้เอากลับไปวางในบล็อกนั้น ปกติผมมักใช้วิธีที่สอง
อาจเป็นเพราะว่าการแก้ไขคราวนี้เล็กน้อย เลยขาดความระมัดระวัง ตอนก๊อปปี้โค้ด A กลับมาแทนที่จะวางไว้ที่บล็อกของ A กลับเอาไปแทนที่โค้ดของ C กลายเป็นจุดพลิกผันที่นึกอย่างไรก็นึกไม่ออก เพราะไม่คิดว่าจะพลาดตรงนี้
แล้วเกิดอะไรขึ้น อธิบายแบบง่าย ๆ โค้ด A include C แต่ถ้าเอาโค้ดนี้ไปใส่ในเป็น C ด้วย กลายเป็นว่าตอนนี้โค้ด C include C กลายเป็นงูกินหางตัวเองไม่รู้จบ จนกระทั่งเบราเซอร์ไม่สามารถทำงานต่อไปได้
บทเรียนคราวนี้ เรื่องแรกเสียใจที่ละเลย เพราะคิดว่าเคสเช่นนี้มีโอกาสเกิดขึ้นน้อยมาก ตอนที่สร้างฟังก์ชั่น include จริง ๆ แล้วสามารถเขียนให้รัดกุมตรวจว่ามีการ include ตัวเองซ้อนกันผิดปกติหรือไม่ เช่น ไม่ยอมให้ include ตัวเองมากกว่า 10 รอบ ก็สามารถควบคุมความผิดพลาดตรงนี้ได้
บทเรียนที่สองที่ต้องเตือนใจตัวเองมาก ๆ ยิ่งรีบยิ่งช้า งานหลุดงานพลาดจนเป็นเรื่องใหญ่มักมาจากเรื่องเล็กอย่างนี้ มากกว่าเรื่องใหญ่ที่เราให้ความระมัดระวัง
เสียเวลาไปหนึ่งวันเต็ม ๆ แทนที่จะได้พักหรือทำอย่างอื่น โชคดีที่เป็นแค่โปรแกรมใช้งานไม่ได้ชั่วคราว ไม่ใช่ความสูญเสียที่เอากลับคืนไม่ได้
Commenti