קלוד קוד ותכנות הגנתי

16/07/2026

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

data = {"x": 10}
if "x" in data:
    print(data["x"])

ברור שה if לא משנה בכלום את ריצת התוכנית אבל הוא מפריע לקרוא את הקוד ומעוות את הכוונה. האם כותבי הקוד התכוונו שאולי בהמשך המילון הזה יגיע מבחוץ? האם הקוד שמדפיס את x צריך לטפל בעוד מילונים? אנחנו לא יודעים.

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

ניקח דוגמה יותר מעניינת מפרויקט maigret. בקובץ report.py של הפרויקט אני מוצא שתי פונקציות: get_plaintext_report ו generate_report_context. הפונקציה generate_report_context מייצרת מילון עם נתונים והפונקציה get_plaintext_report לוקחת את הנתונים ובונה מהם דוח.

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

Search by username Dr.
Evil returned 0 accounts.
Extended info extracted from 0 accounts.

בגלל שבתוך המילון ש generate_report_context מייצרת יש בלוק של טקסט בו כל שורה מופרדת בנקודה ואז רווח ואז פונקציית יצירת הדוח מפעילה:

output = (context['brief'] + " ").replace('. ', '.\n')

כדי להדפיס את הדוח וזה גורם לשבירת השורה באמצע.

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

if "brief_lines" in context:
    output = "\n".join(context['brief_lines'])
else:
    output = (context['brief'] + " ").replace('. ', '.\n')

זה מיותר כיוון שאנחנו יוצרים את המילון וברור שאם יש בו את brief יהיה בו גם את המפתח החדש brief_lines.

הניסיון הראשון שלי לתקן את זה היה עם התוספת הבאה לקובץ CLAUDE.md:

Project Philosophy

- When we replace a mechanism in code don't keep the old one for record or backward compatibility
- Implement minimalistic solutions
- Consider what data is actually in a dictionary and use the correct key, don't cover too much ground

זה לא עבד.

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

בניסיון השלישי הוספתי את הטקסט הבא ל CLAUDE.md ונראה שזה עובד:

Dictionary Defensive Programming

- When you read a key from a dict/object we construct ourselves, access it directly never .get() with a fallback. Fallbacks and defensive access are only for data crossing a trust boundary we don't control: user input, network responses, parsed files

וכן זה מאוד ספציפי למילונים. אבל אולי פה הקסם.

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