บั๊กที่รอด QA เสมอ — เมื่อผลการค้นหาที่ช้ากว่ามาทับผลใหม่
ฟีเจอร์ “พิมพ์แล้วผลเปลี่ยนเองโดยไม่ต้องโหลดหน้าใหม่” เป็นสิ่งที่ผู้ใช้คาดหวังจากทุกระบบสมัยนี้ และเป็นสิ่งที่นักพัฒนาส่วนใหญ่เขียนได้ภายในครึ่งชั่วโมง
แต่โค้ดครึ่งชั่วโมงนั้นมีบั๊กที่ไม่มีทางเจอตอนทดสอบในออฟฟิศ และจะโผล่ที่หน้างานลูกค้าเท่านั้น
ปัญหาจากหน้างาน
พนักงานคีย์ข้อมูลพิมพ์เร็ว บางคนพิมพ์ชื่อลูกค้าจบก่อนที่หน้าจอจะตามทัน และร้านค้าจำนวนมากใช้อินเทอร์เน็ตที่ไม่ได้เร็วเท่าออฟฟิศพัฒนาซอฟต์แวร์
อาการที่ลูกค้าแจ้งเข้ามาคือ “ค้นแล้วผลไม่ตรงกับที่พิมพ์” — พิมพ์คำหนึ่งอยู่ แต่ตารางแสดงผลของอีกคำหนึ่ง ต้องลบทิ้งแล้วพิมพ์ใหม่ช้า ๆ ถึงจะได้ผลที่ถูก
รายงานแบบนี้ไล่หายากมาก เพราะพอทีมงานลองทำตาม ก็ได้ผลถูกต้องทุกครั้ง
ทำไมถึงเกิด
เมื่อผู้ใช้พิมพ์คำว่า “สมชาย” ระบบจะยิงคำขอไปที่เซิร์ฟเวอร์หลายครั้งตามตัวอักษรที่พิมพ์
- คำขอที่ 1 — ค้น “สม”
- คำขอที่ 2 — ค้น “สมชาย”
โดยธรรมชาติเราคาดว่าคำตอบจะกลับมาตามลำดับ แต่เครือข่ายไม่รับประกันเรื่องนี้
คำขอที่ 1 ค้นด้วยคำที่สั้นกว่า จึงได้ผลลัพธ์จำนวนมากกว่า ข้อมูลที่ต้องส่งกลับก็ใหญ่กว่า และถ้าเน็ตช้า มันอาจกลับมาหลังคำขอที่ 2
ผลคือหน้าจอวาดผลของ “สมชาย” ก่อน แล้วถูกทับด้วยผลของ “สม” — ผู้ใช้เห็นผลที่ไม่ตรงกับสิ่งที่พิมพ์อยู่ตรงหน้า
ในออฟฟิศที่เน็ตเร็ว คำตอบกลับมาเร็วกว่าที่คนจะพิมพ์ตัวถัดไปเสมอ ลำดับจึงไม่มีวันสลับ บั๊กนี้จึงรอดการทดสอบทุกครั้ง
สิ่งที่เราทำ
เราไม่ปล่อยให้แต่ละหน้าจอแก้ปัญหานี้เอง แต่ทำเป็นชั้นกลางชั้นเดียวที่ทุกช่องค้นหาในระบบเรียกใช้ร่วมกัน โดยมีการป้องกันสามชั้นซ้อนกัน
ชั้นที่ 1 — หน่วงก่อนยิง
รอให้ผู้ใช้หยุดพิมพ์ประมาณ 300 มิลลิวินาทีก่อนค่อยยิงคำขอ ทำให้การพิมพ์คำหนึ่งยิงคำขอครั้งเดียวแทนที่จะยิงตามจำนวนตัวอักษร
ชั้นนี้ลดโอกาสเกิดปัญหา แต่ไม่ได้แก้ — ถ้าผู้ใช้หยุดคิดกลางคำแล้วพิมพ์ต่อ ก็ยังมีคำขอซ้อนกันได้อยู่ดี
ชั้นที่ 2 — ยกเลิกคำขอเก่า
ก่อนยิงคำขอใหม่ทุกครั้ง เราสั่งยกเลิกคำขอเดิมที่ยังค้างอยู่ ผ่านกลไกมาตรฐานของเบราว์เซอร์
นี่คือข้อที่ถูกมองข้ามมากที่สุด เพราะโค้ดที่ไม่มีบรรทัดนี้ก็ทำงานได้ปกติดี — จนกว่าจะเจอเน็ตช้า
ชั้นที่ 3 — เลขลำดับกำกับ
ทุกคำขอมีเลขลำดับติดไป และเมื่อคำตอบกลับมา ระบบจะเทียบก่อนว่าเลขลำดับตรงกับคำขอล่าสุดหรือไม่ ถ้าเป็นคำตอบของรอบเก่าจะถูกทิ้งโดยไม่วาดลงหน้าจอ
ชั้นนี้มีไว้เผื่อกรณีที่เบราว์เซอร์หรืออุปกรณ์เครือข่ายระหว่างทางยกเลิกคำขอไม่ทันจริง ๆ ซึ่งเกิดขึ้นได้
ตัวเลือกที่เราตัดออก
ทางเลือกที่ง่ายกว่าคือให้เซิร์ฟเวอร์ส่ง JSON กลับมาแล้ววาดตารางด้วย JavaScript ฝั่งหน้าเว็บ แต่เราเลือกขอ HTML ก้อนเดิมที่เซิร์ฟเวอร์เรนเดอร์อยู่แล้วกลับมาสลับทั้งก้อนแทน
เหตุผลคือกฎการแสดงผลของระบบมีเยอะกว่าที่คิด — รูปแบบตัวเลข รูปแบบวันที่ตามการตั้งค่าของแต่ละกิจการ ป้ายสถานะ ลำดับคอลัมน์มาตรฐาน 10 คอลัมน์ ถ้าย้ายไปวาดที่ฝั่งหน้าเว็บ กฎเหล่านี้จะถูกคัดลอกไปอยู่สองที่ แล้ววันหนึ่งจะไม่ตรงกัน
อีกจุดที่จงใจออกแบบคือ สลับทั้งบล็อกผลลัพธ์ ไม่ใช่แค่ตาราง เพราะตัวนับ “พบ N รายการ” กับตัวแบ่งหน้าต้องเปลี่ยนพร้อมตารางเสมอ ไม่งั้นจะเกิดสภาพที่ตารางเหลือ 3 แถวแต่ด้านบนยังเขียนว่า “พบ 2,778 รายการ · หน้า 1/93”
วิธีที่ทำให้ลืมไม่ได้คือให้ทั้งสามอย่างอยู่ในก้อนเดียวกันแล้วสลับทีเดียว ไม่ใช่ต้องอาศัยความจำของคนเขียนว่าต้องอัปเดตทีละชิ้น
และถ้าไม่มี JavaScript
ทั้งหมดนี้เป็นชั้นเสริมที่วางทับฟอร์มค้นหาแบบธรรมดา ถ้าสคริปต์โหลดไม่สำเร็จ ผู้ใช้กด Enter แล้วหน้าเว็บโหลดใหม่พร้อมผลการค้นหาที่ถูกต้องเหมือนเดิม ไม่มีอะไรพัง
ทำไมเราถึงเล่าเรื่องนี้
เพราะมันเป็นตัวอย่างที่ดีของงานประเภทที่ลูกค้าไม่มีทางเห็นว่าเราทำ
ระบบที่ไม่ได้ใส่การป้องกันสามชั้นนี้ก็ยัง “ใช้งานได้” และสาธิตให้ดูได้อย่างราบรื่น ความต่างจะปรากฏก็ต่อเมื่อใช้งานจริงทุกวัน บนเครือข่ายจริง โดยคนที่พิมพ์เร็วกว่าที่ผู้ออกแบบคาดไว้
เราเลือกใส่มันไว้ที่ชั้นกลางที่ทุกหน้าใช้ร่วมกัน เพราะรู้ว่าถ้าปล่อยให้แต่ละหน้าจัดการเอง สุดท้ายจะมีหน้าที่ลืม — และเราเคยเจอมาแล้วจริง ๆ ตอนที่พบว่าแท็บย่อยของหน้ารายละเอียดลูกค้าทั้ง 8 แท็บคัดลอกสคริปต์ก้อนเดียวกันไปวางไว้ โดยไม่มีแท็บไหนเลยที่ยกเลิกคำขอเก่า
บริหารจัดการข้อมูลในองค์กร ครบในที่เดียว