TypeScript ช่วยดักจับบั๊กโง่ๆ ก่อนที่จะหลุดไปถึง production มันจะเตือนคุณเมื่อพิมพ์ชื่อ property ผิด, ลืมใส่ argument, หรือเขียน logic ที่คืนค่าผิดรูปแบบ คุณแก้ไขมัน จน build ผ่าน แล้วก็ส่งงานได้ แต่ static types มีขีดจำกัดที่สำคัญ เมื่อคอมไพเลอร์ทำงานเสร็จ ทุกการระบุ type จะถูกลบออกไปหมด JavaScript engine ที่รันโค้ดของคุณไม่เคยรู้จัก interface, branded types หรือ string literals ที่คุณกำหนดไว้อย่างละเอียดเลย มันรู้จักเพียงแค่ค่า (values) และกฎเกณฑ์ที่แท้จริงของภาษาเท่านั้น
การที่ build ผ่าน ไม่ได้หมายความว่าปลอดภัยในตอน runtime การที่ test ผ่านเป็นสีเขียว ก็ไม่ได้หมายความว่าผู้ใช้จะได้เห็นแอปพลิเคชันที่เสถียร หากโมเดลความคิดของคุณหยุดอยู่แค่ขอบเขตของ TypeScript คุณกำลังบินแบบหลับตาในจุดที่การ crash เกิดขึ้นจริงๆ
ภาพลวงตาในตอน Compile-Time
ระบบ type ทั้งหมดของ TypeScript จะถูกลบออกไประหว่างการคอมไพล์ ลองเปิดไฟล์ JavaScript ที่คอมไพล์แล้วของโปรเจกต์ใดก็ได้ดู แล้วคุณจะไม่พบร่องรอยของ interface, type หรือ generic constraints เลย สิ่งเหล่านี้เป็นเพียงโครงร่าง (scaffolding) ในช่วงเวลาออกแบบเท่านั้น เบราว์เซอร์หรือกระบวนการของ Node.js จะรัน plain JavaScript และค่าที่ไหลผ่านฟังก์ชันของคุณก็ไม่ได้รับประกันว่าจะตรงกับ type ที่คุณประกาศไว้ในกระดาษ
ช่องว่างนี้สำคัญที่สุดตรง "ขอบ" (edges) ของระบบคุณ การตอบสนองจากเครือข่าย (network responses), ข้อมูลจากผู้ใช้ และไลบรารีจากภายนอก สามารถฉีดค่าที่ละเมิด type ของคุณเข้ามาได้ ตัวแปรที่คุณประกาศว่าเป็น strictEmail: string ยังคงสามารถเก็บค่าเป็นตัวเลขได้ในตอน runtime หากมีข้อมูลที่ผิดพลาดส่งผ่าน API ที่ไม่น่าเชื่อถือ TypeScript ไม่สามารถตามโค้ดของคุณไปจนถึง production เพื่อบังคับใช้กฎใดๆ ได้ Runtime ทำงานบนระนาบที่แยกจากกันโดยสิ้นเชิง และการนำทั้งสองระนาบมาปนกันจะนำไปสู่ความล้มเหลวที่ static analysis ไม่มีวันตรวจพบ
เมื่อตัวเลขทรยศคุณ
TypeScript มองเห็น number แต่ JavaScript engine มองเห็น IEEE 754 double-precision float ความแตกต่างนี้ดูเหมือนไม่มีพิษมีภัย จนกระทั่งมันกลายเป็นหายนะ
JavaScript จัดสรรพื้นที่ 64 บิตสำหรับทุกตัวเลข แต่มีเพียง 53 บิตเท่านั้นที่ใช้เก็บ mantissa นั่นทำให้เพดานของจำนวนเต็มที่ปลอดภัย (safe integer ceiling) อยู่ที่ 9,007,199,254,740,991 อะไรที่ใหญ่กว่านั้นจะถูกปัดเศษไปยังค่าที่ใกล้เคียงที่สุดที่สามารถแสดงผลได้ ในทางปฏิบัติ identifier สองตัวที่แตกต่างกันอย่างสิ้นเชิงอาจกลายเป็นค่าเดียวกันภายในแอปพลิเคชันของคุณ
Snowflake IDs และ identifier แบบ 64-bit แบบกระจายตัวอื่นๆ มักจะมีค่าเกินขีดจำกัดนั้น ระบบการเงินที่ติดตามจำนวนเงินมูลค่าสูงในหน่วยเงินย่อยก็อาจจะชนเพดานนี้ได้เช่นกัน อันตรายมักจะปรากฏขึ้นก่อนที่ business logic ของคุณจะเริ่มทำงานเสียอีก: JSON.parse จะแปลง numeric literals ใน payload ให้เป็น JavaScript numbers อย่างรวดเร็ว ซึ่งจะทำให้ความแม่นยำ (precision) ถูกตัดทอนลงอย่างเงียบๆ เมื่อข้อมูลมาถึง นิยาม type ของคุณอาจจะสัญญาว่า id: number แต่ค่าใน runtime อาจจะเสียหายไปแล้วตั้งแต่ก่อนการเรียกฟังก์ชันแรก
วิธีแก้ไขนั้นตรงไปตรงมาแต่ต้องอาศัยวินัยตลอดทั้ง stack ของคุณ ให้เก็บ identifier ขนาดใหญ่เป็น string ในระหว่างการส่งผ่านเครือข่าย ใน JSON schemas และ API contracts ของคุณ ให้กำหนดฟิลด์เหล่านี้เป็น string ไม่ใช่ number หากคุณจำเป็นต้องคำนวณทางคณิตศาสตร์กับค่าที่เกินช่วงที่ปลอดภัย ให้ใช้ BigInt อย่างไรก็ตาม ต้องระวัง: BigInt ไม่สามารถนำมาผสมกับ JavaScript numbers มาตรฐานได้โดยอัตโนมัติ และ JSON.stringify ก็ไม่สามารถ serialize BigInt ได้โดยไม่เกิด error เว้นแต่คุณจะแปลงมันกลับเป็น string ก่อน ให้ปฏิบัติกับ ID เหมือนเป็น opaque tokens โดยค่าเริ่มต้น และจะแปลงเป็นรูปแบบตัวเลขก็ต่อเมื่อคุณอยู่ในโมดูลการคำนวณที่แยกส่วนออกมาที่จำเป็นต้องใช้จริงๆ
