อักขระที่มองไม่เห็น — บั๊กที่ผู้ใช้พิสูจน์ไม่ได้ว่าตัวเองไม่ได้พิมพ์ผิด
ลูกค้าแจ้งเข้ามาว่ากรอกอีเมลไม่ผ่าน ระบบบอกว่ารูปแบบไม่ถูกต้อง
เขาส่งภาพหน้าจอมาให้ดู และอีเมลนั้นถูกต้องทุกตัวอักษร
นี่คือบั๊กประเภทที่เราคิดว่าแย่ที่สุดแบบหนึ่ง — บั๊กที่ผู้ใช้พิสูจน์ไม่ได้ว่าตัวเองไม่ได้ทำผิด
สาเหตุ
อีเมลนั้นมีอักขระความกว้างศูนย์แทรกอยู่ระหว่างตัวอักษรทุกตัว
มันมาจากการคัดลอกข้อความมาจากแอปอื่น — แชท หน้าเว็บ หรือเอกสาร ซึ่งบางแอปแทรกอักขระเหล่านี้เพื่อควบคุมการตัดบรรทัดหรือการแสดงผล แล้วมันติดมากับข้อความตอนคัดลอก
อักขระพวกนี้ไม่มีความกว้าง ไม่มีสี และไม่ทำให้เคอร์เซอร์สะดุด — คุณเลื่อนผ่านมันไปโดยไม่รู้ตัว จะลบก็ไม่รู้ว่ากำลังลบอะไร
ผลคือระบบเห็นข้อความที่ยาวกว่าที่ตาเห็นเกือบสองเท่า และไม่ตรงกับรูปแบบอีเมลที่ควรจะเป็น
ทำไมบั๊กแบบนี้ถึงแย่เป็นพิเศษ
บั๊กทั่วไปผู้ใช้ยังพอเดาทางแก้ได้ แต่บั๊กนี้ไม่มีทางเลย
- ตาเห็นว่าถูก — ตรวจกี่รอบก็ไม่เจอ
- ข้อความผิดพลาดชี้ผิดทาง — ระบบบอกว่า “รูปแบบอีเมลไม่ถูกต้อง” ซึ่งเป็นความจริงในมุมของระบบ แต่ทำให้ผู้ใช้ไปไล่หาสิ่งที่ไม่มีอยู่
- ผู้ใช้เริ่มสงสัยตัวเอง — พอพิมพ์ใหม่ทีละตัวแล้วผ่าน ก็สรุปว่าตัวเองพิมพ์ผิดตั้งแต่แรก ทั้งที่ไม่ใช่
- ส่วนใหญ่ไม่แจ้ง — คนที่เจอจะเลิกคัดลอกแล้วพิมพ์เอาเองเงียบ ๆ ซึ่งแปลว่าเราไม่มีวันรู้ว่ามีปัญหา
เคสที่เข้ามาถึงเราจึงน่าจะเป็นส่วนน้อยของคนที่เจอจริง
อักขระล่องหนมีอะไรบ้าง
มากกว่าที่คนส่วนใหญ่คิด และแต่ละตัวมาจากคนละเหตุผล
| กลุ่ม | ทำหน้าที่อะไร | ติดมาจากไหน |
|---|---|---|
| ความกว้างศูนย์ | บอกจุดที่ตัดบรรทัดได้ โดยไม่กินที่ | เว็บและแอปแชทแทรกให้อัตโนมัติ |
| เครื่องหมายลำดับไบต์ | บอกวิธีเข้ารหัสของไฟล์ | ไฟล์ที่ส่งออกจากโปรแกรมตารางคำนวณ |
| ตัวควบคุมทิศทางข้อความ | สลับทิศการอ่านซ้าย-ขวา | ข้อความที่ปนภาษาอาหรับหรือฮีบรู |
| ตัวเชื่อมคำ | ห้ามตัดบรรทัดตรงนี้ | เอกสารที่จัดหน้ามาแล้ว |
| ยัติภังค์อ่อน | จุดที่ใส่ขีดได้ถ้าจำเป็นต้องตัดคำ | โปรแกรมประมวลผลคำ |
| ช่องว่างไม่ตัดบรรทัด | ช่องว่างที่ห้ามขึ้นบรรทัดใหม่ตรงนั้น | คัดลอกจากหน้าเว็บ |
ทั้งหมดนี้มีเหตุผลที่ถูกต้องของมันเองในบริบทที่มันถูกออกแบบมา ปัญหาคือมันไม่ควรอยู่ในช่องกรอกอีเมล
ทำไมล้างทุกตัวทิ้งให้หมดไม่ได้
ทางแก้ที่ง่ายที่สุดคือล้างอักขระล่องหนทุกตัวออกจากทุกช่องกรอก
เราไม่ทำแบบนั้น เพราะบางตัวมีความหมายจริงในภาษาของคนจริง
- ตัวเชื่อมความกว้างศูนย์ ใช้จริงในภาษาอาหรับ เปอร์เซีย และกลุ่มอักษรอินเดีย เพื่อบังคับให้ตัวอักษรเชื่อมกันหรือแยกจากกัน — ลบทิ้งแปลว่าเขียนชื่อคนผิด
- อีโมจิแบบผสม ใช้ตัวเชื่อมตัวเดียวกันนี้ต่อหลายอีโมจิเข้าเป็นตัวเดียว — ลบทิ้งแล้วอีโมจิครอบครัวหนึ่งตัวจะแตกเป็นคนหลายคนเรียงกัน
การล้างแบบเหมารวมจึงแก้ปัญหาหนึ่งแล้วสร้างอีกปัญหาที่แย่กว่า เพราะการทำให้ชื่อคนผิดเป็นเรื่องที่ให้อภัยยากกว่าการที่ระบบขึ้นข้อความผิดพลาด
นโยบายที่เราเลือก — แยกตามชนิดของช่อง
เราแบ่งช่องกรอกเป็นสองกลุ่มที่ปฏิบัติต่างกัน
กลุ่มที่ 1 — ช่องที่เครื่องต้องอ่าน
อีเมล เลขประจำตัวผู้เสียภาษี เบอร์โทรศัพท์ รหัสไปรษณีย์ รหัสสินค้า
ช่องเหล่านี้ล้างอักขระล่องหนทุกตัวโดยไม่มีข้อยกเว้น
เหตุผลชัดเจน — ไม่มีเลขประจำตัวผู้เสียภาษีไทยที่ถูกต้องตัวไหนมีตัวเชื่อมของภาษาอาหรับอยู่ข้างใน ค่าที่เป็นไปได้ของช่องกลุ่มนี้ถูกจำกัดไว้แล้วโดยรูปแบบของมันเอง อักขระล่องหนที่หลุดเข้ามาจึงเป็นขยะเสมอ
กลุ่มที่ 2 — ช่องข้อความอิสระ
ชื่อ ที่อยู่ หมายเหตุ
ช่องเหล่านี้ล้างเฉพาะตัวที่เป็นขยะแน่ ๆ คือตัวความกว้างศูนย์ เครื่องหมายลำดับไบต์ และตัวควบคุมทิศทาง แต่ไม่แตะตัวเชื่อม เพราะภาษาและอีโมจิใช้จริง
เราเลือกให้ช่องกลุ่มนี้ “สะอาดน้อยกว่า” อย่างตั้งใจ เพราะความเสียหายจากการล้างเกินสูงกว่าความเสียหายจากการล้างขาด
ช่องว่างพิเศษ — ไม่ลบ แต่แปลง
มีตัวหนึ่งที่เราจัดการต่างจากตัวอื่นทั้งหมด คือช่องว่างแบบไม่ตัดบรรทัด
ตัวนี้ติดมากับการคัดลอกจากหน้าเว็บบ่อยมาก และหน้าตาเหมือนช่องว่างธรรมดาทุกประการ
เราไม่ลบทิ้ง แต่แปลงเป็นช่องว่างธรรมดา
เพราะตำแหน่งนั้นคนตั้งใจเว้นวรรคจริง ๆ ถ้าลบทิ้งจะได้ “สมชายใจดี” แทนที่จะเป็น “สมชาย ใจดี” ซึ่งเป็นการทำข้อมูลเสียหายไปอีกแบบหนึ่ง
เสร็จแล้วจึงค่อยตัดช่องว่างหัวท้ายออกตามปกติ
จุดที่อันตรายกว่าการตรวจสอบไม่ผ่าน
ส่วนนี้คือเหตุผลที่งานนี้ใหญ่กว่าที่ดูตอนแรก
ถ้าเราแก้แค่ให้ผ่านการตรวจสอบ อักขระล่องหนจะยังถูกเก็บลงฐานข้อมูล แล้วเดินทางต่อออกไปข้างนอก
- ไฟล์ใบกำกับภาษีอิเล็กทรอนิกส์ — ปลายทางเป็นระบบอัตโนมัติ ซึ่งอาจปฏิเสธไฟล์ หรือแย่กว่านั้นคือรับไปแล้วเก็บข้อมูลที่ไม่ตรงกับที่เราเข้าใจ
- อีเมลที่ส่งถึงลูกค้า — ที่อยู่อีเมลที่มีขยะปนอาจส่งไม่ถึง หรือชื่อผู้รับแสดงผลเพี้ยน
- การค้นหาในระบบของเราเอง — ค้นชื่อลูกค้าแล้วไม่เจอ ทั้งที่ข้อมูลอยู่ในระบบ เพราะข้อความที่เก็บไว้กับที่พิมพ์ค้นไม่เท่ากันในสายตาคอมพิวเตอร์
ข้อสุดท้ายไล่หาสาเหตุยากมาก เพราะมันดูเหมือนปัญหาของระบบค้นหา ไม่ใช่ปัญหาของข้อมูล
เราจึงกำหนดว่าค่าที่ฟังก์ชันคืนกลับมาคือค่าที่จะเก็บลงฐานข้อมูล ไม่ใช่ค่าชั่วคราวที่ใช้ตรวจแล้วทิ้ง และจะมีชุดทดสอบบังคับเรื่องนี้โดยเฉพาะ เพราะเป็นข้อที่ลืมได้ง่ายที่สุด — โค้ดที่ล้างเพื่อตรวจอย่างเดียวหน้าตาเหมือนโค้ดที่ถูกต้องทุกประการ
เรื่องที่เราคิดว่าน่าเล่าที่สุด
ตอนเขียนโค้ดส่วนนี้รอบแรก เราใส่อักขระล่องหนลงในซอร์สโค้ดตรง ๆ เพื่อระบุว่าจะล้างตัวไหนบ้าง
แล้วชั้นจัดการข้อความบางชั้นระหว่างทางก็ทำให้อักขระเหล่านั้นเพี้ยน
พูดอีกอย่างคือ — ไฟล์ที่เขียนขึ้นเพื่อแก้ปัญหาอักขระล่องหน ถูกอักขระล่องหนทำพังเสียเอง
เราจึงเขียนใหม่โดยระบุอักขระด้วยรหัสตัวเลข แทนการใส่ตัวอักษรจริงลงในซอร์ส ซึ่งเป็นตัวเลขธรรมดาที่ไม่มีชั้นไหนทำเพี้ยนได้ พร้อมเขียนเหตุผลกำกับไว้ในไฟล์ เพื่อไม่ให้คนที่มาทีหลัง “จัดระเบียบ” มันกลับไปเป็นแบบเดิมด้วยความหวังดี
บทเรียนที่กว้างกว่านั้นคือ — เมื่อกำลังจัดการกับสิ่งที่มองไม่เห็น อย่าให้สิ่งนั้นอยู่ในโค้ดของคุณในรูปที่มองไม่เห็น
ทยอยทำ ไม่กวาดทั้งระบบในใบเดียว
เมื่อรู้ว่าปัญหานี้มีจริง สิ่งที่อยากทำที่สุดคือไล่ใส่การล้างข้อความให้ทุกช่องกรอกในระบบทันที
เราตั้งใจไม่ทำแบบนั้น
เพราะการแก้ที่แตะทุกช่องกรอกพร้อมกันมีขอบเขตความเสียหายเท่ากับทั้งระบบ ถ้ามีอะไรผิดพลาด เช่นล้างเกินในช่องที่ไม่ควรล้าง จะกระทบทุกหน้าจอพร้อมกันและแยกไม่ออกว่ามาจากจุดไหน
เราจึงเริ่มจากช่องอีเมลลูกค้า ซึ่งเป็นจุดที่เจอปัญหาจริง ให้มันทำงานถูกต้องและมีชุดทดสอบครบก่อน แล้วค่อยขยายไปช่องอื่นทีละกลุ่ม
วิธีนี้ช้ากว่าในระยะสั้น แต่ทุกขั้นที่ขยับพิสูจน์แล้วว่าไม่พัง ซึ่งสำคัญกว่าเมื่อระบบมีลูกค้าใช้งานจริงอยู่ทุกวัน
สรุปสิ่งที่ได้จากเรื่องนี้
- บั๊กที่ผู้ใช้พิสูจน์ไม่ได้ว่าตัวเองไม่ผิด คือบั๊กที่ต้องรีบแก้ที่สุด เพราะเขาจะโทษตัวเองแล้วเงียบไป
- การล้างข้อมูลต้องแยกตามชนิดของช่อง ช่องที่เครื่องอ่านล้างได้เข้ม ช่องที่คนอ่านต้องระวัง เพราะบางอักขระคือส่วนหนึ่งของภาษาจริง
- ล้างเพื่อผ่านการตรวจอย่างเดียวไม่พอ ต้องล้างค่าที่เก็บด้วย ไม่งั้นขยะจะเดินทางออกไปหาปลายทางที่เราควบคุมไม่ได้
- ช่องว่างพิเศษให้แปลง ไม่ใช่ลบ เพราะเจตนาของคนพิมพ์ยังอยู่ตรงนั้น
- ทยอยทำทีละจุด เริ่มจากที่เจอปัญหาจริง ไม่ใช่กวาดทั้งระบบเพราะอยากให้จบในครั้งเดียว
เรื่องนี้เริ่มจากลูกค้าคนเดียวที่กรอกอีเมลไม่ผ่าน และจบลงด้วยการทบทวนว่าข้อความทุกชนิดในระบบเดินทางจากมือผู้ใช้ไปจนถึงกรมสรรพากรอย่างไร — ซึ่งเป็นเหตุผลที่เราคิดว่าคำร้องเรียนเล็ก ๆ คุ้มค่าที่จะตามให้ถึงที่สุดเสมอ
บริหารจัดการข้อมูลในองค์กร ครบในที่เดียว