ข้าม​ไป​ยัง​เนื้อหา

ภาษา​ไทย​ทำให้​โค้ด​พัง​คนละ​ชั้น​กัน​สี่​ชั้น

โค้ด​ที่​จัดการ​ข้อความ​มัก​ถูก​เขียน​และ​ทดสอบ​ด้วย​ภาษา​อังกฤษ พอ​มัน​ทำงาน​ถูก คน​เขียน​ก็​เชื่อ​ว่า มัน​จัดการ “ข้อความ” ได้ ไม่ใช่​แค่ “ข้อความ​ภาษา​อังกฤษ”

หน้า​นี้​ไป​วัด​ว่า​ความ​เชื่อ​นั้น​พัง​ตรง​ไหน​บ้าง คำ​ตอบ​คือ​มัน​พัง คนละ​ชั้น​กัน​สี่​ชั้น และ แต่ละ​ชั้น​พัง​ด้วย​กลไก​คนละ​อย่าง วิธี​แก้​ของ​ชั้น​หนึ่ง​จึง​ช่วย​อีก​ชั้น​ไม่​ได้​เลย

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

ไล่​จาก​ล่าง​ขึ้น​บน แต่ละ​ชั้น​เป็น​หน่วย​คนละ​หน่วย​ของ​สิ่ง​ที่​เรา​เรียก​รวม ๆ ว่า “ตัว​อักษร”

ชั้นหน่วยสิ่ง​ที่​พัง
1byteลบ​อักขระ​ด้วย​การ​ลบ byte แล้ว​อักษร​ไทย​ข้าง​เคียง​เสีย​ไป​ด้วย
2code point\b ของ regex มอง​ไม่​เห็น​ขอบเขต​คำ​ไทย ด่าน​ตรวจ​จึง​เงียบ
3คำไม่มี​ช่องว่าง​คั่น​คำ ตัว index จึง​ตัด​คำ​ผิด​และ​ค้น​ไม่​เจอ
4cultureth-TH ผูก​กับ​ปฏิทิน​พุทธ ปี​เดียวกัน​จึง​ต่าง​กัน 543

หน่วย​ของ​หลักฐาน​ใน​หน้า​นี้​คือ​จำนวนนับ​กับ​ค่า​จริง-เท็จ ไม่มี​วินาที ตัวเลข​ทุก​ตัว​มา​จาก scripts/thai-breaks-code/probe.mjs ที่ commit ไว้​แล้ว รัน​ซ้ำ​ได้​ด้วย npm run verify:thai-text และ CI รัน​ให้​ทุก​ครั้ง​ที่ push

ชั้น​ที่ 1 · byte — ลบ​อักขระ​ตัว​หนึ่ง แล้ว​อีก​สาม​ตัว​หาย​ไป​ด้วย

หัวข้อ​ที่​มีชื่อ​ว่า “ชั้น​ที่ 1 · byte — ลบ​อักขระ​ตัว​หนึ่ง แล้ว​อีก​สาม​ตัว​หาย​ไป​ด้วย”

U+200B เขียน​เป็น UTF-8 ได้ E2 80 8B และ​อักษร​ไทย​บาง​ตัว​ใช้ byte ชุด​เดียวกัน​นี้​เป็น​ส่วนประกอบ

เว็บ​นี้​แทรก​อักขระ zero-width space (U+200B) ลง​ระหว่าง​คำ​ไทย​ทุก​คำ​ตอน build เพื่อ​ให้​ตัว index ค้นหา​เห็น​ขอบเขต​คำ อักขระ​ตัว​นี้​ไม่มี​ความ​กว้าง ผู้​อ่าน​จึง​ไม่​เห็น​มัน แต่​สคริปต์​ที่​ไป​อ่าน​หน้า​เว็บ​จะ​เจอ และ​ต้อง​ลบ​ทิ้ง​ก่อน​เทียบ​ข้อความ

คำถาม​คือ​ลบ​อย่างไร ถ้า​เลือก​เครื่องมือ​ที่​ทำงาน ระดับ byte อย่าง tr -d ผล​จะ​เป็น​แบบ​นี้

อักษรcode pointUTF-8
U+0E0BE0 B8 8B
U+0E40E0 B9 80
U+0E4BE0 B9 8B

สาม byte ของ U+200B คือ E2 80 8B และ​อักษร​สาม​ตัว​ข้าง​บน​ต่าง​ก็​มี 80 หรือ 8B เป็น byte สุดท้าย​ของ​ตัวเอง เครื่องมือ​ที่​สั่ง​ว่า “ลบ byte เหล่า​นี้​ทิ้ง” จึง​ลบ​มัน​ออก​จาก อักษร​ไทย​พวก​นั้น​ด้วย แล้ว byte ที่​เหลือ​ก็​ประกอบ​กลับ​เป็น​อักษร​ไม่​ได้​อีก

ก่อนลบ เราเลือกซ่อนความซับซ้อนไว้เบื้องหลัง
ลบถูกวิธี เราเลือกซ่อนความซับซ้อนไว้เบื้องหลัง
ลบทีละ byte �รา�ลือก�่อนความ�ับ�้อนไว้�บื้องหลัง

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

ที่​เจ็บ​ที่สุด​คือ​มัน​ไม่​แดง

สคริปต์​ไม่ error ไฟล์​ยัง​เปิด​ได้ และ​ถ้า​คน​ตรวจ​อ่าน​ผ่าน ๆ ก็​ยัง​พอ​เดา​ความ​ออก ความ​เสียหาย​จึง​เดินทาง​ต่อ​ไป​ได้​ไกล​มาก​ก่อน​จะ​มี​คน​สังเกต

วิธี​ที่​ถูก​คือ​ใช้​เครื่องมือ​ที่​รู้จัก​การ​เข้า​รหัส แล้ว​ลบ ตัว​อักษร ไม่ใช่​ลบ byte

Terminal window
# ผิด — ลบทีละ byte
tr -d '\342\200\213' < page.html
# ถูก — บอกให้ perl อ่านและเขียนเป็น UTF-8 แล้วลบทีละ code point
perl -CSD -pe 's/\x{200B}//g' < page.html

ชั้น​ที่ 2 · code point — ด่าน​ตรวจ​ที่​ไม่​เคย​ทำงาน​เลย​สัก​ครั้ง

หัวข้อ​ที่​มีชื่อ​ว่า “ชั้น​ที่ 2 · code point — ด่าน​ตรวจ​ที่​ไม่​เคย​ทำงาน​เลย​สัก​ครั้ง”

\b ของ JavaScript นิยาม​ขอบเขต​คำ​บน [A-Za-z0-9_] เท่านั้น หลัง​อักษร​ไทย​จึง​ไม่มี​ขอบเขต​ให้​เจอ

คอร์ส​ที่ 28 ของ​เว็บ​นี้​มี​ด่าน​ตรวจ​ข้อ​หนึ่ง​ที่​ห้าม​บทเรียน​อ้าง​ความเร็ว​เป็น​วินาที เพราะ​ตัวเลข​เวลา​เปลี่ยน​ตาม​เครื่อง ด่าน​นั้น​เขียน​ไว้​แบบ​นี้

/\d+\s*(ms|มิลลิวินาที|วินาที|นาที)\b/

อ่าน​แล้ว​ดู​ครบ มี​ทั้ง​หน่วย​อังกฤษ​และ​หน่วย​ไทย​สาม​แบบ แต่​พอ​เอา​ไป​รัน​จริง​กับ​สี่​ประโยค

ประโยคregex เดิม​จับ​ได้หลัง​แก้​จับ​ได้
ใช้เวลา 12 msจับ​ได้จับ​ได้
ใช้เวลา 12 วินาทีไม่​จับจับ​ได้
ใช้เวลา 12 นาทีไม่​จับจับ​ได้
ใช้เวลา 12 มิลลิวินาทีไม่​จับจับ​ได้

regex เดิม​จับ​ได้ 1 จาก 4 ประโยค และ​ประโยค​เดียว ที่​มัน​จับ​ได้​คือ​ประโยค​ที่​ลงท้าย​ด้วย​หน่วย​ภาษา​อังกฤษ ตัว​เลือก​ภาษา​ไทย​ทั้ง​สาม​ตัว​ไม่​มี​วัน​แมตช์

สาเหตุ​อยู่​ที่​นิยาม​ของ \b มัน​คือ​รอย​ต่อ​ระหว่าง​อักขระ​ที่​อยู่​ใน​ชุด [A-Za-z0-9_] กับ​อักขระ​ที่​ไม่​อยู่​ใน​ชุด​นั้น อักษร​ไทย​ไม่​อยู่​ใน​ชุด ท้าย​คำ​ว่า วินาที จึง​ไม่ใช่​รอย​ต่อ และ​เมื่อ​ไม่มี​รอย​ต่อ ก็​ไม่มี​อะไร​ให้ \b แมตช์

/\d+\s*(ms|มิลลิวินาที|วินาที|นาที)(?![\w])/

เปลี่ยน​เป็น (?![\w]) แล้ว​จับ​ได้ 4 จาก 4 ประโยค เงื่อนไข​กลับ​ด้าน​จาก “ต้อง​มี​รอย​ต่อ” เป็น “ห้าม​ตาม​ด้วย​อักขระ​คำ” ซึ่ง​เป็น​จริง​เมื่อ​จบ​สตริง​ด้วย

ด่าน​ที่​ไม่​ทำงาน อันตราย​กว่า​ด่าน​ที่​ไม่มี

ด่าน​ที่​ไม่มี​อยู่ ทุก​คน​รู้​ว่า​ไม่มี ส่วน​ด่าน​นี้​ขึ้น​เขียว​ทุก​ครั้ง คน​เขียน​บทเรียน​จึง​เชื่อ​ว่า มี​คน​เฝ้า​ให้​อยู่ ประโยค​อย่าง “ใช้​เวลา 12 วินาที” ผ่าน​ฉลุยมา​ตลอด

กฎ​นี้​ใช้ได้​กับ​ทุก regex ที่​จับ​คำ​ไทย ไม่ใช่​แค่​ตัว​นี้ และ​ไม่ใช่​แค่ JavaScript ทุก​ภาษา​ที่​นิยาม \b บน​ชุด​อักขระ​แบบ ASCII มี​ปัญหา​เดียวกัน

ชั้น​นี้​ยัง​มี​อีก​ด้าน​ที่​เงียบ​กว่า คือ​การ​นับ​ความ​ยาว สาม​วิธี​ที่​ดู​สม​เหตุ​สม​ผล​พอกัน ให้​คำ​ตอบ​คนละ​ค่า​บน​คำ​เดียวกัน

คำ.lengthcode pointสิ่ง​ที่​ผู้​อ่าน​เห็น
เพื่อน664
กิน332
น้ำ331
เกี่ยวข้อง10107

คำ​ว่า น้ำ ชัด​ที่สุด .length ตอบ 3 ส่วน​คน​อ่าน​เห็น 1 ตัว เพราะ​สระ​กับ​วรรณยุกต์​เป็น code point แยก ที่​ไป​เกาะ​บน​พยัญชนะ ไม่​ได้​กิน​ที่​ของ​ตัวเอง

ผล​ตก​กับ​ทุก​ที่​ที่​นับ​ความ​ยาว ทั้ง maxLength ของ​ฟอร์ม การ​ตัด​ข้อความ​ด้วย slice และ​กฎ “ย่อหน้า​ห้าม​ยาว​เกิน​กี่​ตัว​อักษร” ที่​เว็บ​นี้​ใช้​เอง ถ้า​อยาก​ได้​จำนวน​ที่​ตรง​กับ​สายตา ต้อง​ใช้ Intl.Segmenter ที่ granularity เป็น grapheme

ภาษา​อังกฤษ​บอก​ขอบเขต​คำ​ด้วย​ช่องว่าง ภาษา​ไทย​ไม่​บอก ใคร​ก็ตาม​ที่​ต้องการ “คำ” จึง​ต้อง​เดา​เอง

เว็บ​นี้​ใช้ Pagefind เป็น​ตัว​ค้นหา และ​มี build step ที่​แทรก U+200B ระหว่าง​คำ​ไทย​ทุก​คำ ก่อน​ส่ง​ให้ Pagefind ทำ index คอมเมนต์​ใน​โค้ด​เขียน​เหตุผล​ไว้​ว่า ถ้า​ไม่​ทำ การ​ค้น​ภาษา​ไทย จะ​พัง — ประโยค​นี้​อยู่​ใน​รี​โป​มา​ตั้งแต่​เดือน​มิถุนายน และ​ไม่​เคย​มี​ใคร​วัด​ว่า​จริง​ไหม

ครึ่ง​แรก — วัด​ว่าการ​แทรก​ช่วย​จริง​หรือ​เปล่า

หัวข้อ​ที่​มีชื่อ​ว่า “ครึ่ง​แรก — วัด​ว่าการ​แทรก​ช่วย​จริง​หรือ​เปล่า”

วิธี​วัด​คือ​ทำ index สอง​ชุด​จาก​หน้า​เดียวกัน ชุด​หนึ่ง​แทรก U+200B อีก​ชุด​ไม่​แทรก แล้ว​ยิง คำ​เดียวกัน 200 คำ​ใส่​ทั้ง​คู่ ความ​จริง​ที่​ใช้​เทียบ​คือ หน้า​ไหน​มี​คำ​นั้น​อยู่​จริง ใน​ข้อความ​ที่​ผู้​อ่าน​เห็น คลัง​ที่​ใช้​มี 60 หน้า และ​ถูก commit ไว้​พร้อม hash 93d7258c0b635f12 ผล​จึง​ไม่​เลื่อน​ตาม​เนื้อหา​ที่​เพิ่ม​เข้า​มา​ทีหลัง

indexrecallจำนวน​คำ​ที่​เข้า index
แทรก U+200B (ของ​จริง​ที่ deploy อยู่)91.1 %3363
ไม่​แทรก​เลย84.4 %4043

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

ที่​สำคัญ​กว่า​คือ ไม่มี​ฝั่ง​ไหน​ชนะ​ทุก​คำ มี​คำ​ที่​การ​ตัด​คำ​ทำให้​ค้น​เจอ น้อย​ลง

คำแทรก U+200Bไม่​แทรก
ฐาน1519
นิยาม1516
เครื่องมือ1112

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

ครึ่ง​หลัง — ผม​วัด​ผิด​ชั้น และ​มัน​คือ​บทเรียน​ของ​บทความ​นี้​เอง

หัวข้อ​ที่​มีชื่อ​ว่า “ครึ่ง​หลัง — ผม​วัด​ผิด​ชั้น และ​มัน​คือ​บทเรียน​ของ​บทความ​นี้​เอง”

ครึ่ง​นี้​เคย​เขียน​ไว้​ว่า ช่อง​ค้นหา​ของ​เว็บ​นี้​คืน​ศูนย์​ผลลัพธ์​ให้​คำ​ว่า ไม่ได้ ทั้ง​ที่​คำ​นั้น​อยู่​ใน​หน้า​ส่วน​ใหญ่​ของ​ไซต์ · ข้อ​อ้าง​นั้น​ผิด และ​ถูก​แก้​เมื่อ 2026-08-19

ที่มา​ของ​ความ​ผิดพลาด​คือ​วิธี​วัด ผม​เรียก pagefind.search() จาก Node ตรง ๆ แล้ว​ได้​ศูนย์​จริง แต่ ผู้​อ่าน​ไม่​ได้​เรียก​ฟังก์ชัน​นั้น เขา​พิมพ์​ลง​ช่อง​ค้นหา พอ​ไป​วัด​ใน​เบราว์เซอร์​จริง คำ​ว่า ไม่ได้ คืน 475 ผลลัพธ์ ตัวเลข​ศูนย์​เป็น​ของ API ที่​ถูก​เรียก​เย็น ๆ ครั้ง​เดียว ยิง​คำ​อื่น​ก่อน​หนึ่ง​ครั้ง​แล้ว​ยิง​ซ้ำ ก็ได้​ผลลัพธ์​ตาม​ปกติ

วัด​ถูก​วิธี แต่​วัด​ผิด​ชั้น

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

สิ่ง​ที่​ยัง​จริง​หลัง​วัด​ใหม่ คำ​ว่า ค้นหา มี​อยู่ 64 หน้า แต่​ช่อง​ค้นหา​คืน 7 หน้า · นั่น​คือ​การ​ค้น​ไม่​เจอ​จริง ๆ และ​มัน​เป็น​คนละ​อาการ​กับ​ที่​เคย​รายงาน​ไว้ ส่วน​คำ​อื่น​ที่​วัด​คู่​กัน ทำงาน​ได้​ดี — ธุรกิจ 197 หน้า​คืน 189 และ ขอบเขต 242 หน้า​คืน 231

ตอน​นี้​มี​ด่าน​คุม​ของ​จริง​แล้ว​ที่ e2e/search-thai-compound.spec.ts ซึ่งขับ​เบราว์เซอร์ แล้ว​พิมพ์​ลง​ช่อง​ค้นหา​เหมือน​ผู้​อ่าน ไม่ใช่​เรียก API · ด่าน​เดิม​ที่​มี​อยู่​ตรวจ​แค่​คำ​เดี่ยว มัน​จึง​เขียว​ทั้ง​ตอน​ที่​ข้อ​อ้าง​นี้​ถูก​และ​ตอน​ที่​มัน​ผิด

th-TH ผูก​กับ​ปฏิทิน​พุทธ​ใน​ข้อมูล CLDR ทุกรันไทม์​ที่​ใช้ ICU จึง​ได้​ผล​เหมือน​กัน​หมด

สาม​ชั้น​แรก​เป็น​เรื่อง​ของ​ข้อความ ชั้น​นี้​ไม่ใช่ มัน​คือ​เรื่อง​ที่​ตัวเลข​ชุด​เดียวกัน ถูก​แสดงออก​มา​คนละ​ค่า​เพราะ locale ของ​ผู้​ใช้

var day = new DateTime(2026, 8, 14);
day.ToString("d", new CultureInfo("th-TH")); // "14/8/2569"
day.Year; // 2026
รันไทม์cultureผลลัพธ์ปฏิทิน​ที่​ถูก​เลือก
.NETen-US8/14/2026GregorianCalendar
.NETth-TH14/8/2569ThaiBuddhistCalendar
Nodeen-US8/14/26gregory
Nodeth-TH14/8/69buddhist

ทั้ง​สอง​รันไทม์​เลือก​ปฏิทิน​พุทธ​ให้​เอง​โดย​ไม่มี​ใคร​สั่ง เพราะ​ทั้ง​คู่​อ่าน​จาก​ฐาน​ข้อมูล CLDR ชุด​เดียวกัน นี่​จึง​ไม่ใช่​นิสัย​ของ .NET และ​ไม่ใช่​บั๊ก มัน​คือ​ค่า​ตั้งต้น​ที่​ถูกต้อง​ตาม​ที่ คน​ไทย​เขียน​วัน​ที่​จริง ๆ

สิ่ง​ที่​ทำให้​มัน​อันตราย​คือ ค่าที่​โปรแกรม​ถือ​อยู่​ไม่​เปลี่ยน day.Year ยัง​เป็น 2026 อยู่​เหมือน​เดิม สิ่ง​ที่​เปลี่ยน​คือ​ตอน​แปลง​เป็น​ข้อความ ปัญหา​จึง​โผล่ ตอน​ข้อความ​นั้น​เดินทาง​กลับ​เข้า​ระบบ เช่น เขียน​ลง​ไฟล์ ส่ง​ขึ้น API หรือ parse กลับ​เป็น​วัน​ที่

กฎ​ที่​ใช้ได้​กับ​ทุก​ภาษา

ผูก culture ให้​ชัดเจน​ทุก​ครั้ง​ที่​แปลง​ระหว่าง​ข้อความ​กับ​ค่า ใช้​ค่า​คงที่​ของ​ระบบ (InvariantCulture ใน .NET) สำหรับ​ข้อมูล​ที่​เครื่อง​อ่าน และ​ใช้ culture ของ​ผู้​ใช้ เฉพาะ​ตอน​แสดง​ผล​ให้​คน​อ่าน​เท่านั้น

หนึ่ง​จุด​ที่​พลาด​กัน​บ่อย​เป็น​พิเศษ​คือ InvariantGlobalization ถ้า​ตั้ง​เป็น true (ค่า​ตั้งต้น​ของ container image หลาย​ตัว) th-TH จะ​กลาย​เป็น​ปฏิทิน​เกรกอ​เรียน​เงียบ ๆ โค้ด​ชุด​เดียวกัน​จึง​ให้​ปี​ต่าง​กัน 543 ระหว่าง​เครื่อง​นัก​พัฒนา​กับ production

ทุก​ชั้น​มี​คน​แก้​ไว้​แล้ว และ​ทุก​ชั้น​ยัง​พัง​อยู่ดี

เรื่อง​ที่​ซ้ำ​กัน​ทั้ง​สี่​ชั้น​ไม่ใช่ “ภาษา​ไทย​ยาก” แต่​เป็น​สาม​ข้อ​นี้

หนึ่ง — ความ​พัง​ไม่​เคย​ส่งเสียง ไม่มี​ชั้น​ไหน throw exception ไม่มี​ชั้น​ไหน​ทำให้​เทสต์แดง tr -d คืน exit code 0 regex ที่​ไม่​แมตช์คือ​ผลลัพธ์​ปกติ​ของ regex ช่อง​ค้นหา​ที่​คืน​ศูนย์ ผลลัพธ์​ก็​หน้าตา​เหมือน​คำ​ที่​ไม่มี​ใน​เว็บ​จริง ๆ

สอง — วิธี​แก้​อยู่​คนละ​ชั้น​กับ​ที่​มัน​พัง build step ที่​แทรก U+200B แก้​ฝั่ง index แต่​คำ​ที่​ค้น​ไม่​เจอ​พัง​ที่​ฝั่ง query การ​เพิ่ม​หน่วย​ภาษา​ไทย​เข้าไป​ใน regex ไม่​ช่วย ถ้า \b ยัง​อยู่ การ​รู้​ว่า​ตัวเอง​อยู่​ชั้น​ไหน​สำคัญ​กว่า​การ​รู้​วิธี​แก้

สาม — ด่าน​ที่​ตรวจ​ครึ่ง​เดียว​คือ​ด่าน​ที่​เขียว​ตลอด เทสต์ค้นหา​ของ​เว็บ​นี้​ยิง​คำ​ไทย​แล้ว ยืนยัน​ว่า​เจอ​ผลลัพธ์ ซึ่ง​เป็น​จริง​ทั้ง​ตอน​ที่ build step ทำงาน​และ​ตอน​ที่​ไม่​ทำงาน มัน​จึง​ยืนยัน​ได้​แค่​ว่า “ค้นหา​ไม่​ตาย” ไม่​ได้​ยืนยัน​สิ่ง​ที่​มัน​ถูก​เขียน​ขึ้น​มา​เพื่อ​ยืนยัน

ข้อ​สาม​คือ​ข้อ​ที่​แพง​ที่สุด และ​มัน​ไม่ใช่​เรื่อง​ของ​ภาษา​ไทย​เลย การ​วัด​กรณี​ตรง​ข้าม​คือ​สิ่ง​ที่ แยก​ด่าน​ออก​จาก​พิธีกรรม ถ้า​เทสต์ผ่าน​ทั้ง​ตอน​ที่​ของ​ทำงาน​และ​ตอน​ที่​ของ​ไม่​ทำงาน มัน​ไม่​ได้​ตรวจ​อะไร​อยู่

  • ลบ​อักขระ อย่า​ลบ byte ใช้​เครื่องมือ​ที่​รู้จัก​การ​เข้า​รหัส perl -CSD ไม่ใช่ tr -d
  • อย่า​ใช้ \b กับ​คำ​ไทย ใช้ (?![\w]) หรือ (?<![\w]) แทน แล้ว​เทสต์ด้วย​ประโยค​ไทย​จริง
  • .length ไม่ใช่​จำนวน​ตัว​อักษร​ที่​คน​เห็น ใช้ Intl.Segmenter เมื่อ​ความ​ยาว​มี​ความหมาย​กับ​ผู้​ใช้
  • ผูก culture ให้​ชัด​ทุก​ครั้ง​ที่​แปลง​ข้อความ​เป็น​ค่า และ​เช็ก InvariantGlobalization ใน container
  • ตรวจ​กรณี​ตรง​ข้าม​เสมอ ปิด​สิ่ง​ที่​คุณ​เพิ่ง​เพิ่ม แล้ว​ดู​ว่า​เทสต์แดง​ไหม ถ้า​ไม่​แดง มัน​ไม่​ได้​ตรวจ​สิ่ง​นั้น

ข้อ​สุดท้าย​เป็น​ข้อ​เดียว​ที่​ไม่​เกี่ยว​กับ​ภาษา​ไทย และ​เป็น​ข้อ​ที่​ทำให้​อีก​สี่​ข้อ​ข้าง​บน ถูก​ค้น​พบ​ตั้งแต่​แรก