← Blog

อักขระที่มองไม่เห็น — บั๊กที่ผู้ใช้พิสูจน์ไม่ได้ว่าตัวเองไม่ได้พิมพ์ผิด

ลูกค้าแจ้งเข้ามาว่ากรอกอีเมลไม่ผ่าน ระบบบอกว่ารูปแบบไม่ถูกต้อง

เขาส่งภาพหน้าจอมาให้ดู และอีเมลนั้นถูกต้องทุกตัวอักษร

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

สาเหตุ

อีเมลนั้นมีอักขระความกว้างศูนย์แทรกอยู่ระหว่างตัวอักษรทุกตัว

มันมาจากการคัดลอกข้อความมาจากแอปอื่น — แชท หน้าเว็บ หรือเอกสาร ซึ่งบางแอปแทรกอักขระเหล่านี้เพื่อควบคุมการตัดบรรทัดหรือการแสดงผล แล้วมันติดมากับข้อความตอนคัดลอก

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

ผลคือระบบเห็นข้อความที่ยาวกว่าที่ตาเห็นเกือบสองเท่า และไม่ตรงกับรูปแบบอีเมลที่ควรจะเป็น

ทำไมบั๊กแบบนี้ถึงแย่เป็นพิเศษ

บั๊กทั่วไปผู้ใช้ยังพอเดาทางแก้ได้ แต่บั๊กนี้ไม่มีทางเลย

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

เคสที่เข้ามาถึงเราจึงน่าจะเป็นส่วนน้อยของคนที่เจอจริง

อักขระล่องหนมีอะไรบ้าง

มากกว่าที่คนส่วนใหญ่คิด และแต่ละตัวมาจากคนละเหตุผล

กลุ่ม ทำหน้าที่อะไร ติดมาจากไหน
ความกว้างศูนย์ บอกจุดที่ตัดบรรทัดได้ โดยไม่กินที่ เว็บและแอปแชทแทรกให้อัตโนมัติ
เครื่องหมายลำดับไบต์ บอกวิธีเข้ารหัสของไฟล์ ไฟล์ที่ส่งออกจากโปรแกรมตารางคำนวณ
ตัวควบคุมทิศทางข้อความ สลับทิศการอ่านซ้าย-ขวา ข้อความที่ปนภาษาอาหรับหรือฮีบรู
ตัวเชื่อมคำ ห้ามตัดบรรทัดตรงนี้ เอกสารที่จัดหน้ามาแล้ว
ยัติภังค์อ่อน จุดที่ใส่ขีดได้ถ้าจำเป็นต้องตัดคำ โปรแกรมประมวลผลคำ
ช่องว่างไม่ตัดบรรทัด ช่องว่างที่ห้ามขึ้นบรรทัดใหม่ตรงนั้น คัดลอกจากหน้าเว็บ

ทั้งหมดนี้มีเหตุผลที่ถูกต้องของมันเองในบริบทที่มันถูกออกแบบมา ปัญหาคือมันไม่ควรอยู่ในช่องกรอกอีเมล

ทำไมล้างทุกตัวทิ้งให้หมดไม่ได้

ทางแก้ที่ง่ายที่สุดคือล้างอักขระล่องหนทุกตัวออกจากทุกช่องกรอก

เราไม่ทำแบบนั้น เพราะบางตัวมีความหมายจริงในภาษาของคนจริง

  • ตัวเชื่อมความกว้างศูนย์ ใช้จริงในภาษาอาหรับ เปอร์เซีย และกลุ่มอักษรอินเดีย เพื่อบังคับให้ตัวอักษรเชื่อมกันหรือแยกจากกัน — ลบทิ้งแปลว่าเขียนชื่อคนผิด
  • อีโมจิแบบผสม ใช้ตัวเชื่อมตัวเดียวกันนี้ต่อหลายอีโมจิเข้าเป็นตัวเดียว — ลบทิ้งแล้วอีโมจิครอบครัวหนึ่งตัวจะแตกเป็นคนหลายคนเรียงกัน

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

นโยบายที่เราเลือก — แยกตามชนิดของช่อง

เราแบ่งช่องกรอกเป็นสองกลุ่มที่ปฏิบัติต่างกัน

กลุ่มที่ 1 — ช่องที่เครื่องต้องอ่าน

อีเมล เลขประจำตัวผู้เสียภาษี เบอร์โทรศัพท์ รหัสไปรษณีย์ รหัสสินค้า

ช่องเหล่านี้ล้างอักขระล่องหนทุกตัวโดยไม่มีข้อยกเว้น

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

กลุ่มที่ 2 — ช่องข้อความอิสระ

ชื่อ ที่อยู่ หมายเหตุ

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

เราเลือกให้ช่องกลุ่มนี้ “สะอาดน้อยกว่า” อย่างตั้งใจ เพราะความเสียหายจากการล้างเกินสูงกว่าความเสียหายจากการล้างขาด

ช่องว่างพิเศษ — ไม่ลบ แต่แปลง

มีตัวหนึ่งที่เราจัดการต่างจากตัวอื่นทั้งหมด คือช่องว่างแบบไม่ตัดบรรทัด

ตัวนี้ติดมากับการคัดลอกจากหน้าเว็บบ่อยมาก และหน้าตาเหมือนช่องว่างธรรมดาทุกประการ

เราไม่ลบทิ้ง แต่แปลงเป็นช่องว่างธรรมดา

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

เสร็จแล้วจึงค่อยตัดช่องว่างหัวท้ายออกตามปกติ

จุดที่อันตรายกว่าการตรวจสอบไม่ผ่าน

ส่วนนี้คือเหตุผลที่งานนี้ใหญ่กว่าที่ดูตอนแรก

ถ้าเราแก้แค่ให้ผ่านการตรวจสอบ อักขระล่องหนจะยังถูกเก็บลงฐานข้อมูล แล้วเดินทางต่อออกไปข้างนอก

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

ข้อสุดท้ายไล่หาสาเหตุยากมาก เพราะมันดูเหมือนปัญหาของระบบค้นหา ไม่ใช่ปัญหาของข้อมูล

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

เรื่องที่เราคิดว่าน่าเล่าที่สุด

ตอนเขียนโค้ดส่วนนี้รอบแรก เราใส่อักขระล่องหนลงในซอร์สโค้ดตรง ๆ เพื่อระบุว่าจะล้างตัวไหนบ้าง

แล้วชั้นจัดการข้อความบางชั้นระหว่างทางก็ทำให้อักขระเหล่านั้นเพี้ยน

พูดอีกอย่างคือ — ไฟล์ที่เขียนขึ้นเพื่อแก้ปัญหาอักขระล่องหน ถูกอักขระล่องหนทำพังเสียเอง

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

บทเรียนที่กว้างกว่านั้นคือ — เมื่อกำลังจัดการกับสิ่งที่มองไม่เห็น อย่าให้สิ่งนั้นอยู่ในโค้ดของคุณในรูปที่มองไม่เห็น

ทยอยทำ ไม่กวาดทั้งระบบในใบเดียว

เมื่อรู้ว่าปัญหานี้มีจริง สิ่งที่อยากทำที่สุดคือไล่ใส่การล้างข้อความให้ทุกช่องกรอกในระบบทันที

เราตั้งใจไม่ทำแบบนั้น

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

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

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

สรุปสิ่งที่ได้จากเรื่องนี้

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

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

Freeable

บริหารจัดการข้อมูลในองค์กร ครบในที่เดียว

ดูรายละเอียด →

อ่านต่อ

Freeable

รหัสผ่าน 2026 — อักษรพิเศษ มาตรฐานสากล กฎหมายไทย และ 2FA/3FA ที่ควรรู้

กฎรหัสผ่านที่เราคุ้นเคย — ต้องมีตัวใหญ่ ตัวเล็ก ตัวเลข อักษรพิเศษ และเปลี่ยนทุก 90 วัน — ถูกมาตรฐานสากลถอดออกไปแล้ว บทความนี้รวบรวมว่าปัจจุบันมาตรฐานว่าอย่างไร กฎหมายไทยกำหนดอะไร และ 2FA ช่วยตรงไหนที่รหัสผ่านช่วยไม่ได้

Freeable

ค่าหนึ่งค่ามี 4 หน้าที่ — มาตรฐานการแทนค่า เก็บ · ส่ง · คำนวณ · แสดง

ปัญหาเรื่องวันที่ไม่ใช่ปัญหาของวันที่ แต่เป็นหนึ่งตัวอย่างของปัญหาตระกูลเดียวกัน — ค่าหนึ่งค่าถูกใช้ทำ 4 หน้าที่ที่ต่างกัน พอรูปแบบของหน้าที่หนึ่งรั่วไปอีกหน้าที่ ตัวเลขก็เพี้ยนเงียบ ๆ

Freeable

เวลาในระบบมีหลายชั้น และมันไม่ตรงกัน — บทเรียนจากบั๊กที่ไล่ 8 วันแล้วสรุปผิดหลายรอบ

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