1. หมายเลขอักขระต่างจากไบต์ที่ใช้เก็บ
สมมติว่าส่งออกเอกสารภาษาเกาหลีแล้วเปิดใหม่ แต่หัวเรื่องกลับเป็นอักขระแปลก ๆ ผู้พิมพ์ไม่ได้เปลี่ยนภาษา แต่อาจใช้กฎอ่านตัวเลขที่บันทึกไว้ไม่ตรงกัน Unicode คือมาตรฐานที่กำหนดหมายเลขเรียกว่าจุดรหัสให้ตัวอักษรและสัญลักษณ์ต่าง ๆ ส่วน UTF-8 คือการเข้ารหัสที่แทนค่าที่เกี่ยวข้องเป็นลำดับไบต์ ตัวอย่างเช่นพยางค์ฮันกึล ‘가’ มีจุดรหัส U+AC00 ข้อความหลัง U+ เป็นเลขฐานสิบหก ไม่ใช่ลำดับตำแหน่งในไฟล์หรือที่อยู่หน่วยความจำ หมายเลขนี้ไม่ได้กำหนดรูปร่างในฟอนต์หรือตำแหน่งพิกเซลบนหน้าจอ จุดรหัสเดียวกันอาจวาดต่างกันตามฟอนต์และสภาพแวดล้อมการจัดข้อความ ดังนั้นการรองรับ Unicode การบันทึกเป็น UTF-8 และการติดตั้งฟอนต์ฮันกึลจึงเกี่ยวข้องกัน แต่ต้องตรวจแยกกัน คำอธิบาย UTF ของ Unicode เป็นจุดเริ่มต้นในการแยกอักขระออกจากรูปแบบที่ใช้จัดเก็บ
ใน UTF-8 ‘가’ กลายเป็นสามไบต์ฐานสิบหก EA B0 80 ขณะที่ A มีจุดรหัส U+0041 และใช้เพียงไบต์เดียวคือ 41 ใน UTF-8 หนึ่งไบต์มีแปดบิต จึงเป็นไปได้ที่อักขระในเอกสารเดียวกันใช้จำนวนไบต์ต่างกัน นามสกุล .txt หรือ .csv เพียงอย่างเดียวก็ไม่บอกการเข้ารหัสจริง UTF-8 แทนค่าสเกลาร์ Unicode ที่ถูกต้องด้วยหนึ่งถึงสี่ไบต์ จึงคำนวณขนาดไฟล์อย่างแม่นยำด้วยการนำจำนวนตัวอักษรที่เห็นไปคูณค่าคงที่ไม่ได้ และไม่ควรนำตัวอย่างพยางค์ฮันกึลนี้ไปใช้กับทุกสัญลักษณ์ที่เกี่ยวกับฮันกึลหรือทุกภาษา การเข้ารหัสอักขระคือกฎแปลงข้อมูลตัวอักษรเป็นไบต์ ไม่ใช่การบีบอัดหรือการเข้ารหัสลับ แม้เป็นประโยคเดียวกัน ขนาดรวมอาจต่างเพราะข้อมูลเสริมของรูปแบบไฟล์ การขึ้นบรรทัดใหม่ หรือการบีบอัดระหว่างส่ง การแยกการแปลงไบต์กับอักขระตาม WHATWG Encoding Standard ออกจากขนาดไฟล์ทั้งหมดและหน้าที่รักษาความลับ จะช่วยจำกัดขอบเขตปัญหา
2. จำนวนไบต์ หน่วยรหัส และอักขระที่มองเห็น
เมื่ออ่าน length ของสตริงใน JavaScript ค่าที่ได้คือจำนวนหน่วยรหัส UTF-16 ไม่ใช่จำนวนไบต์ของไฟล์ UTF-8 หรือจำนวนตัวอักษรตามความรู้สึกของคน ตัวอย่างใบหน้ายิ้ม ‘😀’ เป็นจุดรหัสเดียว U+1F600 แต่ใช้หน่วยรหัส UTF-16 สองหน่วย จึงได้ '😀'.length เท่ากับ 2 ผลเมื่อเข้ารหัส UTF-8 คือ F0 9F 98 80 รวมสี่ไบต์ เลขฐานสิบ 128512 จาก codePointAt(0) คือหมายเลขเดียวกับ U+1F600 ไม่ใช่จำนวนตัวอักษรหรือขนาดไฟล์ ส่วน ‘가’ ตามสามเกณฑ์นี้มีหนึ่งจุดรหัส หนึ่งหน่วยรหัส UTF-16 และสามไบต์ UTF-8 สตริงเดียวกันได้ตัวเลขต่างกันเพราะหน่วยวัดต่างกัน คำอธิบายความยาวสตริงของ MDN ระบุหน่วยของ length ไว้ชัดเจน ตัวเลขนี้อย่างเดียวก็ไม่เพียงพอจะสรุปว่าเอนจิน JavaScript ใช้หน่วยความจำจริงกี่ไบต์
แม้นับจุดรหัสทีละจุด ก็ไม่เท่ากับจำนวนตัวอักษรที่ผู้ใช้เห็นเสมอไป เพราะอักขระพื้นฐานอาจมีเครื่องหมายประกอบ และหลายจุดรหัสอาจรวมเป็นอีโมจิเดียว กฎแบ่งข้อความของ Unicode อธิบายขอบเขตของกลุ่มกราฟีมแบบขยายที่ใช้จัดการกลุ่มเหล่านี้ การกระจายสตริงทีละจุดรหัสด้วย Array.from(text) ก็ไม่ได้นับกลุ่มหลายจุดรหัสเป็นหนึ่งกลุ่มโดยอัตโนมัติ หากต้องการนับในเบราว์เซอร์ ให้ตรวจการรองรับของสภาพแวดล้อมก่อนพิจารณา Intl.Segmenter โดยตั้ง granularity เป็น 'grapheme' อย่างไรก็ตามกลุ่มกราฟีมไม่ได้รับประกันว่าจะตรงหนึ่งต่อหนึ่งกับภาพหนึ่งรูปที่ฟอนต์วาด และความคาดหวังด้านการแก้ไขข้อความอาจต่างตามภาษา ช่องแนะนำตัวควรใช้จำนวนตัวอักษรที่ผู้ใช้เข้าใจ ส่วนข้อจำกัดพื้นที่เก็บหรือการสื่อสารควรใช้จำนวนไบต์แยกกัน หากหน้าจอเขียนว่า ‘20 ตัวอักษร’ แต่ภายในตัดตามหน่วยรหัส UTF-16 อย่างเดียว อาจตัดกลางอีโมจิหรืออักขระประกอบได้
3. เมื่อฮันกึลเพี้ยน ให้แยกการบันทึก ส่ง และแสดงผล
อักขระเพี้ยนอาจเกิดเมื่อการเข้ารหัสที่ใช้เขียนไบต์ต่างจากกฎถอดรหัสที่ใช้อ่าน ไฟล์ที่บันทึกเป็น UTF-8 แต่ถูกตีความเป็นการเข้ารหัสอื่นอาจแสดงอักขระผิด ทั้งที่ตัวไฟล์ไม่ได้เปลี่ยน สำหรับ HTML ต้องตรวจรูปแบบบันทึกจริง charset ใน Content-Type ของการตอบกลับ HTTP และคำประกาศ meta charset ช่วงต้นเอกสารร่วมกัน แอตทริบิวต์ HTML lang='ko' บอกภาษาของเนื้อหา ซึ่งมีหน้าที่ต่างจาก charset ที่เลือกวิธีอ่านไบต์ฮันกึล แนวทางประกาศการเข้ารหัสของ W3C อธิบายว่าต้องไม่เพียงเปลี่ยนคำประกาศเป็น UTF-8 แต่ต้องบันทึกไฟล์จริงเป็น UTF-8 และทำให้คำประกาศของเซิร์ฟเวอร์กับเอกสารตรงกันด้วย เครื่องหมายระบุการเข้ารหัสอาจมีผลต่ออันดับความสำคัญของการตัดสิน จึงไม่ควรคิดว่า meta หนึ่งตัวจะแทนข้อมูลอื่นได้เสมอ หากเปิดไฟล์โดยตรงกับรับผ่านเซิร์ฟเวอร์แล้วได้ต่างกัน การตั้งค่าช่วงส่งเป็นเบาะแสสำคัญ
ปัญหาฟอนต์ไม่มีรูปตัวอักษรที่ต้องใช้จนแสดงเป็นสี่เหลี่ยมอาจมีสาเหตุต่างจากการถอดรหัสผิด ถ้าข้อมูลอักขระถูกแต่หารูปมาวาดไม่ได้ การเปลี่ยนการเข้ารหัสที่ใช้บันทึกก็ไม่แก้ปัญหา ในทางกลับกัน เปลี่ยนฟอนต์ก็ไม่ทำให้อักขระที่ถูกอ่านเป็นตัวอื่นแล้วกลับมา เมื่อตรวจสอบให้เก็บสำเนาไฟล์ปัญหา แล้วเทียบการเข้ารหัสที่โปรแกรมแก้ไขเลือก คำประกาศในการตอบกลับเซิร์ฟเวอร์ อักขระที่เบราว์เซอร์ตีความ และฟอนต์ที่ใช้ตามลำดับ อย่าตัดสินจากรูปร่างบนจออย่างเดียว ต้องตรวจด้วยว่าเมื่อคัดลอกส่วนเดียวกันได้จุดรหัสอะไร การจดว่าผิดเฉพาะบางเบราว์เซอร์หรือโปรแกรมแก้ไขอื่นก็อ่านไฟล์เดียวกันผิด จะช่วยแยกการแสดงผลออกจากการเปลี่ยนข้อมูล การบันทึกซ้ำจนกว่าจะเห็นตัวอักษรปกติอาจทำให้ไบต์ต้นฉบับหาย หากทุกคำประกาศของหน้าถูกแล้วแต่มีเฉพาะบทความหนึ่งผิด ควรตามดูด้วยว่าเนื้อหาเปลี่ยนช่วงใดก่อนเข้าฐานข้อมูล
4. ตรวจค่าเดียวกันด้วย TextEncoder และ TextDecoder
เริ่มการทดลองง่าย ๆ ด้วยสตริงสั้นที่เขียนเอง TextEncoder แปลงสตริง JavaScript เป็นอาร์เรย์ไบต์ UTF-8 ผลของ new TextEncoder().encode('가') เมื่ออ่านแบบฐานสิบหกคือ EA B0 80 และ length ของอาร์เรย์คือ 3 เครื่องมือพัฒนาอาจแสดงค่าเดียวกันเป็นฐานสิบ 234, 176, 128 ซึ่งไม่ใช่ข้อมูลคนละชุด แต่เป็นความต่างของฐานตัวเลข เมื่อเทียบกับ length ปกติของสตริงเดียวกันที่เป็น 1 จะเห็นความต่างของหน่วย หากอ่านอาร์เรย์นี้ด้วย new TextDecoder('utf-8').decode(bytes) จะได้ ‘가’ โดย bytes หมายถึงอาร์เรย์ไบต์ที่สร้างก่อนหน้า ตัวเข้ารหัสนี้เป็นเครื่องมือสร้าง UTF-8 ไม่ใช่ตัวแปลงอเนกประสงค์ที่เลือกบันทึกเป็นการเข้ารหัสเก่าใดก็ได้ เมื่อตรวจไฟล์จริงต้องใช้ไบต์ต้นฉบับที่ได้มาก่อน การนำประโยคบนหน้าจอที่ถูกอ่านผิดไปเข้ารหัส UTF-8 อีกครั้ง ไม่พิสูจน์ว่าไฟล์เริ่มต้นมีไบต์ใด
หากกำหนด 'utf-8' กับ { fatal: true } ให้ ตัวสร้าง TextDecoder เมื่อพบข้อมูลไม่ตรงกฎจะจัดการเป็นข้อผิดพลาด แทนการแทนที่อย่างเงียบ ๆ เช่นไบต์ FF เพียงตัวเดียวไม่ใช่อินพุต UTF-8 ที่ถูกต้อง การถอดรหัสในโหมดนี้จึงล้มเหลว ในโหมดแทนที่ซึ่งเป็นค่าเริ่มต้น ส่วนที่ผิดอาจปรากฏเป็นอักขระแทน U+FFFD แต่การไม่เกิดข้อผิดพลาดไม่ได้แปลว่ารู้การเข้ารหัสเดิมแล้วหรือยืนยันเนื้อหาถูกต้อง ไบต์บางชุดใช้ได้กับหลายการเข้ารหัส และผลที่เปลี่ยนเป็นตัวอื่นแล้วก็อาจถูกบันทึกใหม่เป็น UTF-8 ที่ถูกกฎ การตรวจนี้บอกเพียงว่าตัวถอดรหัสที่เลือกยอมรับอินพุตได้หรือไม่ ไม่ได้ตรวจแหล่งเอกสาร ความตรงกับต้นฉบับ หรือการถูกแก้ระหว่างทางโดยอัตโนมัติ
5. การปรับรูปมาตรฐานเป็นอีกขั้นตอนหนึ่ง
สตริงสองชุดที่อ่านถูกต้องก็อาจมีลำดับจุดรหัสภายในต่างกัน ‘가’ อาจแทนด้วย U+AC00 ตัวเดียว หรือเป็นลำดับพยัญชนะต้นแบบประกอบ U+1100 และสระ U+1161 สองค่าหลังต่างจากการเรียงอักษรจาโมแบบเข้ากันได้ที่ใช้ทั่วไป ‘ㄱ’ และ ‘ㅏ’ ตรง ๆ จึงต้องตรวจหมายเลขด้วย ปกติทั้งสองรูปแสดงเป็นพยางค์เดียวกัน แต่การเทียบสตริงดิบของ JavaScript อาจไม่ถือว่าเท่ากัน หากใช้ NFC ตาม กฎการปรับรูปมาตรฐานของ Unicode กับทั้งสองชุด ก็จะเปรียบเทียบตัวอย่างที่สมมูลแบบคาโนนิคัลนี้ในรูปเดียวกันได้ ส่วน NFD ใช้การแยกองค์ประกอบแบบคาโนนิคัล ในตัวอย่างนี้ UTF-8 ของ NFC ใช้สามไบต์ แต่รูปจาโมสองตัวของ NFD ใช้หกไบต์ หน้าตาเหมือนกันจึงอาจใช้พื้นที่เก็บต่างกัน เมื่อคำค้นกับชื่อที่เก็บดูเหมือนกันแต่จับคู่ไม่ได้ ให้ตรวจความต่างของการแทนรูปนี้ แยกจากอักขระเพี้ยนเพราะอ่านไบต์ผิด
รูปมาตรฐานที่เลือกขึ้นอยู่กับวัตถุประสงค์ด้วย NFC จัดการความสมมูลแบบคาโนนิคัล ส่วน NFKC รวมการแยกองค์ประกอบแบบเข้ากันได้ จึงอาจเปลี่ยนรูปอินพุตที่เดิมต่างกันได้กว้างกว่า เช่นตัวเลขเต็มความกว้างหรือตัวเลขในวงกลม การทำค่าที่ใช้เทียบเพื่อค้นหาอาจได้ประโยชน์ แต่ความต่างนั้นอาจมีความหมายในสัญลักษณ์คณิตศาสตร์หรือเอกสารต้นฉบับ จึงควรเก็บต้นฉบับ แล้วเลือกรูปที่ใช้กับสำเนาสำหรับเปรียบเทียบตามความจำเป็น การปรับรูปมาตรฐานไม่แทนการเทียบตัวพิมพ์ใหญ่เล็ก การเรียงตามภาษา หรือการหาคำออกเสียงเหมือนกัน การแปลงชื่อไฟล์หรือรหัสบัญชีที่มีอยู่ทั้งหมด อาจทำให้รายการต่างกันรวมเป็นค่าเทียบเดียวกัน จึงต้องทบทวนกฎตั้งชื่อและวิธีจัดการค่าชนกันของบริการก่อน การเห็นรูปร่างคล้ายกันไม่ได้หมายความว่าการเก็บอักขระเหล่านั้นทั้งหมดเป็นค่าเดียวคือเป้าหมายทั่วไปของการปรับรูปมาตรฐาน
6. เก็บต้นฉบับและบันทึกขอบเขตการแปลง
เมื่อจำกัดสาเหตุความเสียหาย ให้เก็บไบต์เดิมและเส้นทางที่ได้ไฟล์มาก่อนเขียนทับไฟล์แรก ทดลองอ่านสำเนาด้วยการเข้ารหัสที่คาดไว้ แล้วตรวจว่าหลังบันทึกผลที่ดูถูกต้องอีกครั้ง เนื้อหายังคงเดิมหรือไม่ เครื่องหมายเริ่มต้น UTF-8 ใน คำอธิบาย BOM ของ W3C คือ EF BB BF แต่ไบต์เหล่านี้ไม่จำเป็นต้องมี UTF-8 ไม่ต้องเลือกลำดับไบต์ จึงไม่ควรตีความเครื่องหมายนี้ว่าทำหน้าที่เหมือนการเลือกลำดับไบต์ใน UTF-16 การเพิ่มหรือลบ BOM ก็ต่างจากการแปลงไบต์เนื้อหาไปเป็นการเข้ารหัสอื่น หากค่าที่อ่านไม่ได้ถูกเปลี่ยนเป็นอักขระแทนหรือเครื่องหมายคำถามแล้วบันทึกไว้ หลายค่าเดิมอาจรวมเป็นค่าเดียว จึงรับประกันการกู้คืนอัตโนมัติไม่ได้ ต้องทราบการเข้ารหัสเริ่มต้นและประวัติการแปลงก่อนจึงประเมินโอกาสย้อนกลับได้ การปรับรูปมาตรฐานไม่ใช่เครื่องมือกู้ไบต์ที่สูญไป
เมื่อพัฒนาความสามารถหลายภาษา อย่าจบการทดสอบด้วยภาษาอังกฤษบรรทัดเดียว ให้รวมพยางค์ฮันกึล ตัวละตินมีเครื่องหมายประกอบ อีโมจิในระนาบเสริม และข้อความเขียนขวาไปซ้ายอย่างอาหรับหรือฮีบรูในกลุ่มประโยคสั้นเดียวกัน แล้วเทียบการป้อน บันทึก อ่านกลับ และส่งออก แต่ละขั้นต้องตรวจรูปลักษณ์ จุดรหัส จำนวนไบต์ UTF-8 และเกณฑ์นับอักขระที่ผลิตภัณฑ์กำหนด ทิศเขียนไม่ใช่คุณสมบัติที่แก้ด้วยการเปลี่ยน UTF-8 เป็นการเข้ารหัสอื่น ต้องตรวจภาษาและทิศของหน้าจอแยกด้วย เมื่อแก้ข้อจำกัดการป้อนหรือกฎเทียบคำค้น ก็ต้องรักษาต้นฉบับและอธิบายเกณฑ์ที่เปลี่ยน ตรวจด้วยว่าตัวเลขคำแนะนำในเบราว์เซอร์กับเกณฑ์รับของเซิร์ฟเวอร์ใช้หน่วยเดียวกันหรือไม่ ถ้าจำเป็นต้องมีขีดจำกัดต่างกันให้แสดงแต่ละแบบ ตัวเลขและตัวอย่างโค้ดในบทความแสดงการแปลงสตริงสั้น ไม่ใช่ผลวัดเหตุขัดข้องของบริการเฉพาะ การตรวจตามลำดับตั้งแต่หมายเลขอักขระ ไบต์ที่เก็บ หน่วยสตริง ไปจนถึงกฎแสดงและเปรียบเทียบ จะช่วยทำซ้ำให้เห็นว่าแต่ละขอบเขตเปลี่ยนอะไร