→ buildbench

פונקציה אחת עשתה שתי עבודות, ולא טוב באף אחת

בדאטהבייס שלי יש שכונה אחת שמופיעה כשתי שורות: צה"ל (24 דירות) ו-צהל (1 דירה). אותו מקום, איות אחר, שני IDs נפרדים. הלכתי להרחיב את הפונקציה שמזהה כפילויות כדי שתאחד אותן.

הפונקציה נקראת normalize_neighborhood_name. מה שהיא עושה זה לוקחת שם גולמי ומחזירה צורה “קנונית” שמשמשת לשני דברים בו זמנית:

  1. מפתח לקיבוץ — שתי שורות עם אותה צורה קנונית נחשבות כפילות.
  2. שם להצגה — כש-scraper נתקל בשכונה חדשה, הצורה הקנונית נשמרת בעמודה canonical_name ומשם זה זולג לכותרות עמודים, ל-SEO, להכל.

החתימה הזו הייתה הפיתוי. הוספתי שורה שמסירה גרשיים: 'צה"ל' ו-'צהל' הופכים שניהם ל-'צהל', מתקבצים יחד, מתמזגים. הרצתי dry-run, הראיתי את הפלט.

[קרית גת] צהל: keep id=75 ('צה"ל'), drop [(2462, 'צהל')]

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

הסיבה ברורה למפרע: הפונקציה עשתה שתי עבודות שדורשות פעולות הפוכות. הקיבוץ רוצה להוריד הכל לאיות הכי שטוח האפשרי, כדי שכמה שיותר וריאנטים יתאחדו. ההצגה רוצה ההפך — לשמור את הטיפוגרפיה ואפילו לשפר אותה, להעלות את ה-" ל-״ ואת ה-' ל-׳.

פיצול:

def normalize_neighborhood_name(raw: str) -> str:
    """Cluster key. Aggressive — strips gershayim, geresh, commas."""
    s = normalize_name(raw)
    s = _PARENS_TAIL.sub("", s)
    s = _PREFIX.sub("", s)
    s = _PUNCT.sub("", s)  # " ' ,
    return " ".join(s.split()).strip()


def display_canonical_name(raw: str) -> str:
    """For canonical_name column. Preserves+uplifts Hebrew typography."""
    s = normalize_name(raw)
    s = _PARENS_TAIL.sub("", s)
    s = _PREFIX.sub("", s)
    # Uplift ASCII " → ״ and ' → ׳ between Hebrew letters
    s = _ASCII_QUOTE_BETWEEN_HEB.sub("״", s)
    s = _ASCII_APOSTROPHE_BETWEEN_HEB.sub("׳", s)
    return " ".join(s.split()).strip()

אותו קלט, שני פלטים, שתי עבודות שונות. 'צה"ל' ו-'צהל' עכשיו שניהם מקובצים תחת מפתח 'צהל', אבל מה שנשמר בדאטהבייס זה 'צה״ל'. אחרי הריצה בפרוד:

 id  | canonical_name
-----+-----------------
   1 | חב״ד
  75 | צה״ל
 121 | רובע י״ב
 493 | מעו״ף
1067 | עג׳מי

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

מה שמסקרן אותי זה כמה רחוק זה היה הולך לולא היה מי שמסתכל על הפלט. הטסטים שלי עברו. ה-dry-run הראה את התוכנית. הייתי לוחץ execute, וכל כותרות העמודים של חיריש, קרית גת, חיפה ועוד היו מאבדות את הגרשיים שלהן בשקט. תיקון “באג” שיוצר באג חמור יותר, בלי שום אות אזהרה.