• בלוג
  • את הטעות הזאת אפילו TypeScript לא הצליח לתפוס

את הטעות הזאת אפילו TypeScript לא הצליח לתפוס

26/01/2023

בדיוק כשחשבתי שמצאתי את האור ו TypeScript יתקן לי את כל הבאגים יחסינו הגיעו למשבר. הכל התחיל בפונקציה תמימה שלוקחת מחרוזת, מחליפה בתוכה כמה סימנים ומחזירה את הטקסט המוחלף:

function replaceByDictionary(text: string, replacements: Record<string, string>) {
    let interpolatedText = text;

    for (const [key, value] of Object.entries(replacements)) {
        interpolatedText = interpolatedText.replace(key, value);
    }
}

בלי לשים לב לבאג המשכתי להשתמש בפונקציה כדי לשלוח את הטקסט המוחלף לשרת:

const replacedText = replaceByDictionary("I love TypeScript", { "I": "Everyone" });
await fetch(`https://tocode.requestcatcher.com/test?text=${replacedText}`, { method: 'POST' });

רק כדי להישאר עם לב (וקוד) שבור כשגיליתי שהבקשה שתכל'ס נשלחה לשרת היתה עם ה URL:

POST /test?text=undefined

מה קרה כאן? למה טייפסקריפט פספס? התשובה פשוטה - הסקת טיפוסים אוטומטית גרמה לטייפסקריפט להגדיר את replacedText בתור void, ואופרטור הגרש ההפוך יודע לקבל משתני void בתוך מחרוזות. חוקי לגמרי, ובכלל לא מה שהתכוונתי.

הפיתרון? כמו תמיד בתכנות אין פיתרונות קסם, אבל כן יש שתי אפשרויות מרכזיות:

אפשר להחליט שאנחנו לא אוהבים את הסקת הטיפוסים של טייפסקריפט, ומגדירים את replacedText בתור string בעצמנו. זה אומר לכתוב:

const replacedText: string = replaceByDictionary("I love TypeScript", { "I": "Everyone" });

ועל זה טייפסקריפט כבר יצעק.

או (ולדעתי עדיף) להחליט להוציא לפונקציה את הקוד ששולח טקסט לשרת, ואז נקבל:

async function postText(text: string) {
    await fetch(`https://tocode.requestcatcher.com/test?text=${replacedText}`, { method: 'POST' });
}

const replacedText = replaceByDictionary("I love TypeScript", { "I": "Everyone" });
await postText(replacedText);

בגישה כזאת אנחנו גם שומרים על הגמישות לשנות את קוד השליחה לשרת בעתיד, גם מפרידים בין הלוגיקה (לעדכן את הטקסט) לבין מה עושים עם התוצאה (שולחים לשרת), והכי חשוב, מקבלים מטייפסקריפט את ההגנה שרצינו.