คู่มือฉบับสมบูรณ์เกี่ยวกับ TypeScript assertion functions เรียนรู้วิธีเชื่อมช่องว่างระหว่าง compile-time และ runtime ตรวจสอบข้อมูล และเขียนโค้ดที่ปลอดภัยและทนทานยิ่งขึ้นพร้อมตัวอย่าง
TypeScript Assertion Functions: คู่มือฉบับสมบูรณ์เพื่อความปลอดภัยของประเภทข้อมูลขณะรันไทม์
ในโลกของการพัฒนาเว็บ ข้อตกลงระหว่างความคาดหวังของโค้ดและข้อมูลที่ได้รับจริงมักจะเปราะบาง TypeScript ได้ปฏิวัติวิธีการเขียน JavaScript โดยการมอบระบบประเภทข้อมูลแบบสถิต (static type system) ที่ทรงพลัง ซึ่งช่วยดักจับข้อบกพร่องนับไม่ถ้วนก่อนที่จะไปถึงขั้น production อย่างไรก็ตาม เครือข่ายความปลอดภัยนี้ส่วนใหญ่มีอยู่เฉพาะในขั้นตอน compile-time เท่านั้น จะเกิดอะไรขึ้นเมื่อแอปพลิเคชันที่พิมพ์ประเภทไว้อย่างสวยงามของคุณได้รับข้อมูลที่ยุ่งเหยิงและคาดเดาไม่ได้จากโลกภายนอกในขณะ runtime? นี่คือจุดที่ assertion functions ของ TypeScript กลายเป็นเครื่องมือที่ขาดไม่ได้สำหรับการสร้างแอปพลิเคชันที่ทนทานอย่างแท้จริง
คู่มือฉบับสมบูรณ์นี้จะพาคุณเจาะลึกเกี่ยวกับ assertion functions เราจะสำรวจว่าทำไมมันถึงจำเป็น วิธีสร้างมันขึ้นมาตั้งแต่ต้น และวิธีนำไปใช้กับสถานการณ์จริงที่พบบ่อย ในตอนท้าย คุณจะพร้อมที่จะเขียนโค้ดที่ไม่เพียงแต่ปลอดภัยในเรื่องประเภทข้อมูลตอน compile-time แต่ยังยืดหยุ่นและคาดเดาได้ในขณะ runtime อีกด้วย
ช่องว่างที่ยิ่งใหญ่: Compile-Time กับ Runtime
เพื่อให้เข้าใจคุณค่าของ assertion functions อย่างแท้จริง เราต้องเข้าใจความท้าทายพื้นฐานที่มันแก้ไขก่อน นั่นคือช่องว่างระหว่างโลกของ TypeScript ในตอน compile-time และโลกของ JavaScript ในขณะ runtime
สวรรค์แห่ง Compile-Time ของ TypeScript
เมื่อคุณเขียนโค้ด TypeScript คุณกำลังทำงานอยู่ในสวรรค์ของนักพัฒนา TypeScript compiler (tsc
) ทำหน้าที่เป็นผู้ช่วยที่คอยระแวดระวัง วิเคราะห์โค้ดของคุณเทียบกับประเภทที่คุณกำหนดไว้ มันจะตรวจสอบ:
- การส่งประเภทข้อมูลที่ไม่ถูกต้องไปยังฟังก์ชัน
- การเข้าถึง property ที่ไม่มีอยู่บนอ็อบเจกต์
- การเรียกใช้ตัวแปรที่อาจเป็น
null
หรือundefined
กระบวนการนี้เกิดขึ้น ก่อน ที่โค้ดของคุณจะถูกรัน ผลลัพธ์สุดท้ายคือ JavaScript ธรรมดาที่ถูกลบ type annotations ทั้งหมดออกไป ลองนึกภาพ TypeScript เป็นเหมือนพิมพ์เขียวทางสถาปัตยกรรมโดยละเอียดของอาคาร มันช่วยให้แน่ใจว่าแผนทั้งหมดนั้นสมเหตุสมผล การวัดขนาดถูกต้อง และรับประกันความสมบูรณ์ของโครงสร้างบนกระดาษ
ความเป็นจริงขณะ Runtime ของ JavaScript
เมื่อ TypeScript ของคุณถูกคอมไพล์เป็น JavaScript และทำงานในเบราว์เซอร์หรือสภาพแวดล้อมของ Node.js แล้ว static types ทั้งหมดจะหายไป ตอนนี้โค้ดของคุณกำลังทำงานอยู่ในโลกที่ไม่หยุดนิ่งและคาดเดาไม่ได้ของ runtime มันต้องจัดการกับข้อมูลจากแหล่งที่ควบคุมไม่ได้ เช่น:
- API Responses: บริการฝั่ง backend อาจเปลี่ยนแปลงโครงสร้างข้อมูลโดยไม่คาดคิด
- User Input: ข้อมูลจากฟอร์ม HTML จะถูกถือว่าเป็นสตริงเสมอ ไม่ว่า input type จะเป็นอะไรก็ตาม
- Local Storage: ข้อมูลที่ดึงมาจาก
localStorage
เป็นสตริงเสมอและจำเป็นต้องถูกแยกวิเคราะห์ (parse) - Environment Variables: สิ่งเหล่านี้มักเป็นสตริงและอาจไม่มีอยู่เลยก็ได้
เพื่อใช้อุปมาอุปไมยของเรา runtime ก็คือสถานที่ก่อสร้าง พิมพ์เขียวสมบูรณ์แบบ แต่วัสดุที่ส่งมา (ข้อมูล) อาจมีขนาดผิด ประเภทผิด หรืออาจหายไปเลยก็ได้ หากคุณพยายามสร้างด้วยวัสดุที่ผิดพลาดเหล่านี้ โครงสร้างของคุณจะพังทลาย นี่คือจุดที่เกิดข้อผิดพลาดขณะ runtime ซึ่งมักจะนำไปสู่การแครชและข้อบกพร่องอย่าง "Cannot read properties of undefined"
เข้าสู่ Assertion Functions: การเชื่อมช่องว่าง
แล้วเราจะบังคับใช้พิมพ์เขียว TypeScript ของเรากับวัสดุที่คาดเดาไม่ได้ของ runtime ได้อย่างไร? เราต้องการกลไกที่สามารถตรวจสอบข้อมูล *ขณะที่มันมาถึง* และยืนยันว่ามันตรงกับความคาดหวังของเรา นี่คือสิ่งที่ assertion functions ทำได้อย่างแม่นยำ
Assertion Function คืออะไร?
Assertion function เป็นฟังก์ชันชนิดพิเศษใน TypeScript ที่มีวัตถุประสงค์สำคัญสองประการ:
- การตรวจสอบขณะ Runtime: มันทำการตรวจสอบค่าหรือเงื่อนไข หากการตรวจสอบล้มเหลว มันจะโยนข้อผิดพลาด (throw an error) ซึ่งจะหยุดการทำงานของเส้นทางโค้ดนั้นทันที สิ่งนี้จะป้องกันไม่ให้ข้อมูลที่ไม่ถูกต้องแพร่กระจายเข้าไปในแอปพลิเคชันของคุณ
- การจำกัดประเภทข้อมูลให้แคบลงขณะ Compile-Time: หากการตรวจสอบสำเร็จ (กล่าวคือ ไม่มีการโยนข้อผิดพลาด) มันจะส่งสัญญาณไปยัง TypeScript compiler ว่าประเภทของค่านั้นมีความเฉพาะเจาะจงมากขึ้นแล้ว คอมไพเลอร์จะเชื่อถือการยืนยันนี้และอนุญาตให้คุณใช้ค่าตามประเภทที่ยืนยันแล้วสำหรับส่วนที่เหลือของขอบเขตนั้น
ความมหัศจรรย์อยู่ที่ลายเซ็น (signature) ของฟังก์ชัน ซึ่งใช้คีย์เวิร์ด asserts
มีสองรูปแบบหลักคือ:
asserts condition [is type]
: รูปแบบนี้ยืนยันว่าcondition
บางอย่างเป็น truthy คุณสามารถเพิ่มis type
(type predicate) เพื่อจำกัดประเภทของตัวแปรให้แคบลงได้asserts this is type
: ใช้ภายในเมธอดของคลาสเพื่อยืนยันประเภทของthis
context
ประเด็นสำคัญคือพฤติกรรม "โยนข้อผิดพลาดเมื่อล้มเหลว" ซึ่งแตกต่างจากการตรวจสอบด้วย if
ทั่วไป การยืนยันจะประกาศว่า: "เงื่อนไขนี้ ต้อง เป็นจริงเพื่อให้โปรแกรมดำเนินต่อไปได้ หากไม่เป็นเช่นนั้น ถือเป็นสถานะที่ผิดปกติ และเราควรหยุดทันที"
การสร้าง Assertion Function แรกของคุณ: ตัวอย่างที่ใช้งานได้จริง
มาเริ่มกันที่หนึ่งในปัญหาที่พบบ่อยที่สุดใน JavaScript และ TypeScript: การจัดการกับค่าที่อาจเป็น null
หรือ undefined
ปัญหา: Nulls ที่ไม่พึงประสงค์
ลองจินตนาการถึงฟังก์ชันที่รับอ็อบเจกต์ user ที่เป็นทางเลือก (optional) และต้องการบันทึกชื่อของผู้ใช้ การตรวจสอบค่า null อย่างเข้มงวดของ TypeScript จะเตือนเราอย่างถูกต้องเกี่ยวกับข้อผิดพลาดที่อาจเกิดขึ้น
interface User {
name: string;
email: string;
}
function logUserName(user: User | undefined) {
// 🚨 ข้อผิดพลาด TypeScript: 'user' อาจเป็น 'undefined'
console.log(user.name.toUpperCase());
}
วิธีมาตรฐานในการแก้ไขปัญหานี้คือการใช้การตรวจสอบด้วย if
:
function logUserName(user: User | undefined) {
if (user) {
// ภายในบล็อกนี้ TypeScript จะรู้ว่า 'user' เป็นประเภท 'User'
console.log(user.name.toUpperCase());
} else {
console.error('User is not provided.');
}
}
วิธีนี้ใช้ได้ผล แต่ถ้าการที่ `user` เป็น `undefined` เป็นข้อผิดพลาดที่ไม่สามารถกู้คืนได้ในบริบทนี้ล่ะ? เราไม่ต้องการให้ฟังก์ชันทำงานต่อไปอย่างเงียบๆ เราต้องการให้มันล้มเหลวอย่างชัดเจน สิ่งนี้นำไปสู่การเขียน guard clauses ที่ซ้ำซ้อน
ทางออก: Assertion Function `assertIsDefined`
มาสร้าง assertion function ที่สามารถนำกลับมาใช้ใหม่ได้เพื่อจัดการกับรูปแบบนี้อย่างสง่างาม
// assertion function ที่นำกลับมาใช้ใหม่ได้ของเรา
function assertIsDefined<T>(value: T, message: string = "Value is not defined"): asserts value is NonNullable<T> {
if (value === undefined || value === null) {
throw new Error(message);
}
}
// มาลองใช้กัน!
interface User {
name: string;
email: string;
}
function logUserName(user: User | undefined) {
assertIsDefined(user, "User object must be provided to log name.");
// ไม่มีข้อผิดพลาด! ตอนนี้ TypeScript รู้แล้วว่า 'user' เป็นประเภท 'User'
// ประเภทข้อมูลถูกจำกัดให้แคบลงจาก 'User | undefined' เป็น 'User'
console.log(user.name.toUpperCase());
}
// ตัวอย่างการใช้งาน:
const validUser = { name: 'Alice', email: 'alice@example.com' };
logUserName(validUser); // แสดงผล "ALICE"
const invalidUser = undefined;
try {
logUserName(invalidUser); // โยน Error: "User object must be provided to log name."
} catch (error) {
console.error(error.message);
}
การวิเคราะห์ลายเซ็นของ Assertion
มาวิเคราะห์ลายเซ็นนี้กัน: asserts value is NonNullable<T>
asserts
: นี่คือคีย์เวิร์ดพิเศษของ TypeScript ที่เปลี่ยนฟังก์ชันนี้ให้เป็น assertion functionvalue
: หมายถึงพารามิเตอร์ตัวแรกของฟังก์ชัน (ในกรณีของเราคือตัวแปรชื่อ `value`) มันบอก TypeScript ว่าตัวแปรใดที่ควรจะถูกจำกัดประเภทให้แคบลงis NonNullable<T>
: นี่คือ type predicate มันบอกคอมไพเลอร์ว่าหากฟังก์ชันไม่โยนข้อผิดพลาด ประเภทของ `value` จะกลายเป็นNonNullable<T>
ซึ่งNonNullable
เป็น utility type ใน TypeScript ที่จะลบnull
และundefined
ออกจากประเภทข้อมูล
กรณีการใช้งานจริงสำหรับ Assertion Functions
เมื่อเราเข้าใจพื้นฐานแล้ว มาสำรวจวิธีการนำ assertion functions ไปใช้เพื่อแก้ปัญหาในโลกแห่งความเป็นจริงที่พบบ่อยกัน พวกมันมีประสิทธิภาพมากที่สุดที่ขอบเขตของแอปพลิเคชันของคุณ ซึ่งเป็นจุดที่ข้อมูลภายนอกที่ไม่มีประเภทเข้ามาในระบบของคุณ
กรณีการใช้งานที่ 1: การตรวจสอบการตอบสนองจาก API
นี่น่าจะเป็นกรณีการใช้งานที่สำคัญที่สุด ข้อมูลจากคำขอ fetch
นั้นโดยเนื้อแท้แล้วไม่น่าเชื่อถือ TypeScript จะกำหนดประเภทผลลัพธ์ของ `response.json()` เป็น `Promise
สถานการณ์
เรากำลังดึงข้อมูลผู้ใช้จาก API เราคาดว่ามันจะตรงกับ `User` interface ของเรา แต่เราไม่สามารถแน่ใจได้
interface User {
id: number;
name: string;
email: string;
}
// type guard ทั่วไป (คืนค่าเป็น boolean)
function isUser(data: unknown): data is User {
return (
typeof data === 'object' &&
data !== null &&
'id' in data && typeof (data as any).id === 'number' &&
'name' in data && typeof (data as any).name === 'string' &&
'email' in data && typeof (data as any).email === 'string'
);
}
// assertion function ใหม่ของเรา
function assertIsUser(data: unknown): asserts data is User {
if (!isUser(data)) {
throw new TypeError('Invalid User data received from API.');
}
}
async function fetchAndProcessUser(userId: number) {
const response = await fetch(`https://api.example.com/users/${userId}`);
const data: unknown = await response.json();
// ยืนยันรูปแบบข้อมูลที่ขอบเขต
assertIsUser(data);
// จากจุดนี้เป็นต้นไป 'data' จะถูกพิมพ์ประเภทเป็น 'User' อย่างปลอดภัย
// ไม่ต้องมีการตรวจสอบ 'if' หรือการแปลงประเภทอีกต่อไป!
console.log(`Processing user: ${data.name.toUpperCase()} (${data.email})`);
}
fetchAndProcessUser(1);
ทำไมสิ่งนี้ถึงทรงพลัง: ด้วยการเรียกใช้ `assertIsUser(data)` ทันทีหลังจากได้รับการตอบสนอง เราได้สร้าง "ประตูนิรภัย" ขึ้นมา โค้ดใดๆ ที่ตามมาสามารถถือว่า `data` เป็น `User` ได้อย่างมั่นใจ สิ่งนี้ช่วยแยกตรรกะการตรวจสอบออกจากตรรกะทางธุรกิจ ทำให้โค้ดสะอาดและอ่านง่ายขึ้นมาก
กรณีการใช้งานที่ 2: การตรวจสอบว่า Environment Variables มีอยู่จริง
แอปพลิเคชันฝั่งเซิร์ฟเวอร์ (เช่น ใน Node.js) พึ่งพา environment variables อย่างมากในการกำหนดค่า การเข้าถึง `process.env.MY_VAR` จะได้ประเภทเป็น `string | undefined` สิ่งนี้บังคับให้คุณต้องตรวจสอบการมีอยู่ของมันทุกที่ที่ใช้ ซึ่งน่าเบื่อและเสี่ยงต่อข้อผิดพลาด
สถานการณ์
แอปพลิเคชันของเราต้องการ API key และ database URL จาก environment variables เพื่อเริ่มต้นทำงาน หากไม่มี แอปพลิเคชันจะไม่สามารถทำงานได้และควรจะแครชทันทีพร้อมข้อความแสดงข้อผิดพลาดที่ชัดเจน
// ในไฟล์ utility เช่น 'config.ts'
export function getEnvVar(key: string): string {
const value = process.env[key];
if (value === undefined) {
throw new Error(`FATAL: Environment variable ${key} is not set.`);
}
return value;
}
// เวอร์ชันที่ทรงพลังกว่าโดยใช้ assertions
function assertEnvVar(key: string): asserts key is keyof NodeJS.ProcessEnv {
if (process.env[key] === undefined) {
throw new Error(`FATAL: Environment variable ${key} is not set.`);
}
}
// ใน entry point ของแอปพลิเคชันของคุณ เช่น 'index.ts'
function startServer() {
// ทำการตรวจสอบทั้งหมดตอนเริ่มต้น
assertEnvVar('API_KEY');
assertEnvVar('DATABASE_URL');
const apiKey = process.env.API_KEY;
const dbUrl = process.env.DATABASE_URL;
// ตอนนี้ TypeScript รู้ว่า apiKey และ dbUrl เป็นสตริง ไม่ใช่ 'string | undefined'
// รับประกันได้ว่าแอปพลิเคชันของคุณมีการกำหนดค่าที่จำเป็น
console.log('API Key length:', apiKey.length);
console.log('Connecting to DB:', dbUrl.toLowerCase());
// ... ตรรกะการเริ่มต้นเซิร์ฟเวอร์ที่เหลือ
}
startServer();
ทำไมสิ่งนี้ถึงทรงพลัง: รูปแบบนี้เรียกว่า "fail-fast" (ล้มเหลวอย่างรวดเร็ว) คุณตรวจสอบการกำหนดค่าที่สำคัญทั้งหมดเพียงครั้งเดียวในช่วงเริ่มต้นของวงจรชีวิตแอปพลิเคชันของคุณ หากมีปัญหา มันจะล้มเหลวทันทีพร้อมข้อผิดพลาดที่สื่อความหมาย ซึ่งง่ายต่อการดีบักมากกว่าการแครชอย่างลึกลับที่เกิดขึ้นในภายหลังเมื่อมีการใช้ตัวแปรที่หายไปในที่สุด
กรณีการใช้งานที่ 3: การทำงานกับ DOM
เมื่อคุณค้นหา DOM เช่น ด้วย `document.querySelector` ผลลัพธ์ที่ได้คือ `Element | null` หากคุณแน่ใจว่าองค์ประกอบนั้นมีอยู่ (เช่น `div` หลักของแอปพลิเคชัน) การตรวจสอบ `null` ตลอดเวลาอาจเป็นเรื่องยุ่งยาก
สถานการณ์
เรามีไฟล์ HTML ที่มี `
` และสคริปต์ของเราต้องการแนบเนื้อหาเข้าไปในนั้น เรารู้ว่ามันมีอยู่
// นำ assertion ทั่วไปของเรากลับมาใช้ใหม่จากก่อนหน้านี้
function assertIsDefined<T>(value: T, message: string = "Value is not defined"): asserts value is NonNullable<T> {
if (value === undefined || value === null) {
throw new Error(message);
}
}
// assertion ที่เฉพาะเจาะจงมากขึ้นสำหรับองค์ประกอบ DOM
function assertQuerySelector<T extends Element>(selector: string, constructor?: new () => T): T {
const element = document.querySelector(selector);
assertIsDefined(element, `FATAL: Element with selector '${selector}' not found in the DOM.`);
// ทางเลือก: ตรวจสอบว่าเป็นองค์ประกอบประเภทที่ถูกต้องหรือไม่
if (constructor && !(element instanceof constructor)) {
throw new TypeError(`Element '${selector}' is not an instance of ${constructor.name}`);
}
return element as T;
}
// การใช้งาน
const appRoot = document.querySelector('#app-root');
assertIsDefined(appRoot, 'Could not find the main application root element.');
// หลังจากการยืนยัน appRoot จะมีประเภทเป็น 'Element' ไม่ใช่ 'Element | null'
appRoot.innerHTML = 'Hello, World!
';
// การใช้ helper ที่เฉพาะเจาะจงมากขึ้น
const submitButton = assertQuerySelector<HTMLButtonElement>('#submit-btn', HTMLButtonElement);
// 'submitButton' ตอนนี้ถูกพิมพ์ประเภทเป็น HTMLButtonElement อย่างถูกต้อง
submitButton.disabled = true;
ทำไมสิ่งนี้ถึงทรงพลัง: มันช่วยให้คุณสามารถแสดง invariant ซึ่งเป็นเงื่อนไขที่คุณรู้ว่าเป็นจริงเกี่ยวกับสภาพแวดล้อมของคุณ มันช่วยลดโค้ดการตรวจสอบค่า null ที่รกและบันทึกการพึ่งพาของสคริปต์กับโครงสร้าง DOM ที่เฉพาะเจาะจงได้อย่างชัดเจน หากโครงสร้างเปลี่ยนไป คุณจะได้รับข้อผิดพลาดที่ชัดเจนและทันที
Assertion Functions เทียบกับทางเลือกอื่น ๆ
สิ่งสำคัญคือต้องรู้ว่าเมื่อใดควรใช้ assertion function เทียบกับเทคนิคการจำกัดประเภทอื่น ๆ เช่น type guards หรือ type casting
เทคนิค | ไวยากรณ์ | พฤติกรรมเมื่อล้มเหลว | เหมาะสำหรับ |
---|---|---|---|
Type Guards | value is Type |
คืนค่า false |
การควบคุมการไหลของโปรแกรม (if/else ) เมื่อมีเส้นทางโค้ดทางเลือกที่ถูกต้องสำหรับกรณีที่ "ไม่พึงประสงค์" เช่น "ถ้าเป็นสตริง ให้ประมวลผล; มิฉะนั้น ให้ใช้ค่าเริ่มต้น" |
Assertion Functions | asserts value is Type |
โยน Error |
การบังคับใช้เงื่อนไขที่ไม่เปลี่ยนรูป (invariants) เมื่อเงื่อนไข ต้อง เป็นจริงเพื่อให้โปรแกรมทำงานต่อไปได้อย่างถูกต้อง เส้นทางที่ "ไม่พึงประสงค์" คือข้อผิดพลาดที่ไม่สามารถกู้คืนได้ เช่น "การตอบสนองจาก API ต้อง เป็นอ็อบเจกต์ User" |
Type Casting | value as Type |
ไม่มีผลกระทบขณะรันไทม์ | กรณีที่พบได้ไม่บ่อยซึ่งคุณในฐานะนักพัฒนา รู้ดีกว่าคอมไพเลอร์และได้ทำการตรวจสอบที่จำเป็นแล้ว มันไม่ได้ให้ความปลอดภัยขณะรันไทม์เลยและควรใช้อย่างระมัดระวัง การใช้มากเกินไปถือเป็น "code smell" |
แนวทางสำคัญ
ถามตัวเองว่า: "จะเกิดอะไรขึ้นถ้าการตรวจสอบนี้ล้มเหลว?"
- หากมีเส้นทางทางเลือกที่ถูกต้อง (เช่น แสดงปุ่มเข้าสู่ระบบหากผู้ใช้ยังไม่ได้รับการรับรองความถูกต้อง) ให้ใช้ type guard กับบล็อก
if/else
- หากการตรวจสอบที่ล้มเหลวหมายความว่าโปรแกรมของคุณอยู่ในสถานะที่ไม่ถูกต้องและไม่สามารถทำงานต่อได้อย่างปลอดภัย ให้ใช้ assertion function
- หากคุณกำลังลบล้างการทำงานของคอมไพเลอร์โดยไม่มีการตรวจสอบขณะรันไทม์ คุณกำลังใช้ type cast โปรดระวังให้มาก
รูปแบบขั้นสูงและแนวทางปฏิบัติที่ดีที่สุด
1. สร้างไลบรารี Assertion ส่วนกลาง
อย่ากระจาย assertion functions ไปทั่วโค้ดเบสของคุณ รวบรวมไว้ในไฟล์ utility เฉพาะ เช่น src/utils/assertions.ts
สิ่งนี้ส่งเสริมการนำกลับมาใช้ใหม่ ความสอดคล้อง และทำให้ตรรกะการตรวจสอบของคุณหาง่ายและทดสอบได้ง่าย
// src/utils/assertions.ts
export function assert(condition: unknown, message: string): asserts condition {
if (!condition) {
throw new Error(message);
}
}
export function assertIsDefined<T>(value: T): asserts value is NonNullable<T> {
assert(value !== null && value !== undefined, 'This value must be defined.');
}
export function assertIsString(value: unknown): asserts value is string {
assert(typeof value === 'string', 'This value must be a string.');
}
// ... และอื่นๆ
2. โยนข้อผิดพลาดที่มีความหมาย
ข้อความแสดงข้อผิดพลาดจาก assertion ที่ล้มเหลวเป็นเบาะแสแรกของคุณระหว่างการดีบัก ทำให้มันมีประโยชน์! ข้อความทั่วไปเช่น "Assertion failed" ไม่ได้ช่วยอะไรเลย แต่ควรให้บริบทแทน:
- อะไร ที่กำลังถูกตรวจสอบ?
- อะไร คือค่า/ประเภทที่คาดหวัง?
- อะไร คือค่า/ประเภทจริงที่ได้รับ? (ระวังอย่าบันทึกข้อมูลที่ละเอียดอ่อน)
function assertIsUser(data: unknown): asserts data is User {
if (!isUser(data)) {
// ไม่ดี: throw new Error('Invalid data');
// ดี:
throw new TypeError(`Expected data to be a User object, but received ${JSON.stringify(data)}`);
}
}
3. คำนึงถึงประสิทธิภาพ
Assertion functions คือการตรวจสอบขณะรันไทม์ ซึ่งหมายความว่ามันใช้รอบการทำงานของ CPU สิ่งนี้เป็นที่ยอมรับและเป็นที่ต้องการอย่างสมบูรณ์ที่ขอบเขตของแอปพลิเคชันของคุณ (การรับข้อมูลจาก API, การโหลดการกำหนดค่า) อย่างไรก็ตาม หลีกเลี่ยงการวาง assertions ที่ซับซ้อนภายในเส้นทางโค้ดที่ต้องการประสิทธิภาพสูง เช่น ลูปที่ทำงานหลายพันครั้งต่อวินาที ใช้มันในจุดที่ต้นทุนของการตรวจสอบนั้นน้อยมากเมื่อเทียบกับการดำเนินการที่กำลังทำ (เช่น คำขอเครือข่าย)
สรุป: การเขียนโค้ดด้วยความมั่นใจ
TypeScript assertion functions เป็นมากกว่าฟีเจอร์เฉพาะกลุ่ม มันเป็นเครื่องมือพื้นฐานสำหรับการเขียนแอปพลิเคชันระดับ production ที่ทนทาน พวกมันช่วยให้คุณสามารถเชื่อมช่องว่างที่สำคัญระหว่างทฤษฎีในตอน compile-time และความเป็นจริงในขณะ runtime
ด้วยการนำ assertion functions มาใช้ คุณสามารถ:
- บังคับใช้ Invariants: ประกาศเงื่อนไขที่ต้องเป็นจริงอย่างเป็นทางการ ทำให้สมมติฐานของโค้ดของคุณชัดเจน
- ล้มเหลวอย่างรวดเร็วและชัดเจน: ดักจับปัญหาความสมบูรณ์ของข้อมูลที่ต้นทาง ป้องกันไม่ให้เกิดข้อบกพร่องที่ซ่อนเร้นและดีบักยากในภายหลัง
- ปรับปรุงความชัดเจนของโค้ด: ลดการตรวจสอบ
if
ที่ซ้อนกันและการแปลงประเภท ส่งผลให้ตรรกะทางธุรกิจสะอาดขึ้น เป็นเส้นตรงมากขึ้น และมีการอธิบายในตัว - เพิ่มความมั่นใจ: เขียนโค้ดด้วยความมั่นใจว่าประเภทข้อมูลของคุณไม่ใช่แค่คำแนะนำสำหรับคอมไพเลอร์ แต่มีการบังคับใช้อย่างจริงจังเมื่อโค้ดทำงาน
ครั้งต่อไปที่คุณดึงข้อมูลจาก API, อ่านไฟล์การกำหนดค่า, หรือประมวลผลข้อมูลจากผู้ใช้ อย่าเพียงแค่แปลงประเภทแล้วหวังว่าจะดีที่สุด จงยืนยันมัน (Assert it) สร้างประตูนิรภัยที่ขอบของระบบของคุณ ตัวคุณในอนาคต—และทีมของคุณ—จะขอบคุณสำหรับโค้ดที่ทนทาน คาดเดาได้ และยืดหยุ่นที่คุณได้เขียนขึ้น