הבלוג של ינון פרק

טיפים קצרים וחדשות למתכנתים

בדיקות של AI נגד בדיקות של בני אדם

26/12/2025

איזה כיף לעשות Code Review ל AI! נתתי לו לכתוב משחק סנייק עם בדיקות בפרויקט ריק, כלומר לא היו דוגמאות איזה בדיקות צריך לכתוב ואיזה בדיקות לא. התוצאה הזכירה לי ש AI כמעט תמיד יכתוב קוד שנראה טוב וזה חלק ממה שמאוד מבלבל בעבודה איתו.

הוא התחיל את קובץ הבדיקות בהגדרת אוביקט mock:

mockContext = {
  clearRect: vi.fn(),
  fillRect: vi.fn(),
  fillText: vi.fn(),
  measureText: vi.fn(() => ({ width: 100 })),
  beginPath: vi.fn(),
  arc: vi.fn(),
  fill: vi.fn(),
  stroke: vi.fn(),
  moveTo: vi.fn(),
  lineTo: vi.fn(),
  textAlign: 'left',
  textBaseline: 'alphabetic',
} as unknown as CanvasRenderingContext2D;

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

הצעד הבא זה איפה שדברים הופכים מעניינים עם בדיקות כמו:

it('should update snake head position correctly', () => {
  render(<Home />);
  const initialCalls = mockContext.fillRect.mock.calls.length;

  act(() => {
    vi.advanceTimersByTime(150);
  });

  expect(mockContext.fillRect.mock.calls.length).toBeGreaterThan(initialCalls);
});

לבדיקה יש כותבת "צריך לעדכן את ראש הנחש בצורה נכונה" אבל הבדיקה עצמה רק מוודאת שפונקציית fillRect נקראה אחרי זמן שהוגדר מראש. הבדיקה הזאת גרועה כי:

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

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

  3. היא משתמשת בפרוקסי - מספר הקריאות ל fillRect במקום לבדוק את הדבר האמיתי.

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

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

עוד מאותו דבר

25/12/2025

קלוד קוד יודע לפתור את כל Advent Of Code של השנה בשעתיים. זה בטוח יותר מהר ממני וכנראה יותר מהר מרוב בני האדם שניסו. וככל שהכלים האלה משתפרים אנחנו מבינים טוב יותר את העבודה שלנו כמהנדסי תוכנה:

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

  2. תושיה - למצוא דרכים בטוחות וטובות לעשות דברים גם כשהרעיון הראשון לא עובד.

  3. עבודת צוות.

  4. תקשורת טובה עם עמיתים ומנהלים.

  5. אמינות.

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

  7. הבנת המסגרת, המגבלות, היכולות, הארגון.

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

  1. האם AI יכול לבנות את זה?

  2. אם לא, איך צריכה להיראות המערכת כדי ש AI יוכל לבנות את זה?

אז ה AI טעה בשם של הפונקציה

24/12/2025

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

token_translations.each do |tt|
  word = tt.original_text
  translation = tt.translation

רואים את הבעיה? אפילו לפני שקלוד מתחיל לממש אנחנו מבינים שמשהו לא מסתדר, האם צריך את הסיומת _text או לא? ואכן כשביקשתי מקלוד לממש את הלולאה במקום אחר הוא כתב:

token_translations.each do |tt|
  word = tt.original_text
  translation = tt.translation_text

ואני אפילו לא יכול להאשים אותו. זה נראה כל כך יותר נכון ככה.

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

כזה ניסיתי: Agent OS

23/12/2025

אנחנו כבר יודעים שהגדרה טובה של המשימה היא המפתח להצלחה בעבודה עם סוכני קידוד ולחלופין שבחירת מילים לא מספיק טובה בפרומפט יכולה להרוס את כל הפיצ'ר, אבל דבר אחד עדיין לא ברור כשאנחנו עדיין מנסים להבין מה ההשפעה של סוכני קידוד על תהליך העבודה שלנו כמפתחים - מהו שיווי המשקל הנכון בין כתיבת פרומפטים לכתיבת קוד? האם עיקר העבודה שלנו תצטרך להיות ניסוח קבצי טקסט בצורה מספיק מדויקת, או שקוד הוא ה Spec המדויק ביותר ועלינו להשאר ברמת הקוד?

התומכים בגישה הראשונה כבר יצרו שיטת עבודה מלאה שנקראת Spec Driven Development. הרעיון הוא לדחות את כתיבת הקוד כמה שיותר, להשתמש בסוכני AI כדי לכתוב אפיונים ולבנות אפיון כל כך טוב שאפילו סוכן קידוד יוכל להשתמש בו כדי לבנות את הפיצ'ר. אחד הכלים שמקדמים גישה זו נקרא Agent OS ואפשר לקרוא עליו ולהתקין אותו (לגמרי בחינם) מהאתר:

https://buildermethods.com/agent-os

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

אחרי התקנת Agent OS לתוך פרויקט הסוכן ישקיע דקות ארוכות בניתוח קוד הפרויקט כדי להבין מה המטרה שלו, מה הסטאק הטכנולוגי, מה החלקים המרכזיים שבו ויבנה לעצמו שלושה קבצי טקסט שהולכים ללוות אותנו לכל אורך חיי הפרויקט: mission.md, roadmap.md ו tech-stack.md. הוא באמת כותב המון שם המון. הבעיה הראשונה בעיניי עם כמות הטקסט הזאת היא שמישהו צריך לתחזק את זה. תיאורטית אולי מישהו תכנן שסוכן הקידוד יתחזק את הקבצים האלה כשהגדרת המוצר משתנה אבל במציאות אנחנו יודעים שטקסט מיותר בפרומפטים רק מסבך סוכני קידוד ולבני אדם קשה לתחזק קבצי טקסט.

אחרי הגדרת המוצר Agent OS ממשיך לפיתוח פיצ'ר וכאן דברים נהיים מעניינים. אני רגיל בפיתוח פיצ'ר עם AI לכתוב פרומפט ראשוני, אולי לשכלל קצת את הפרומפט בעזרת AI אחר ולתת לסוכן לקודד. מקסימום אם יש פה משימה מסובכת אני אבקש ממנו לתכנן תוכנית פעולה לקובץ טקסט ואז אתן לו לרוץ על התוכנית. ב Agent OS בניית פיצ'ר מורכבת ממספר שלבים:

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

  2. ממסמך הדרישות הסוכן יוצר מסמך אפיון שכולל כותרות כמו "מטרת הפיצ'ר", "User Stories", "עיצוב ויזואלי", "קוד קיים שכדאי להשתמש", ו"מחוץ לסקופ".

  3. אחרי מסמך האפיון הסוכן מייצר מסמך משימות שבנוי כמו Checklist וכל פעם שהוא מסיים משימה הוא מסמן לעצמו x לידה.

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

מסקנות בפועל:

  1. בניית פיצ'ר ניסיון לקחה לי יותר זמן מאשר פיתוח של הפיצ'ר בעצמי.

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

  3. אבל - מסמכי האפיון כן שיפרו משמעותית את הביצועים של סוכן הקידוד בהשוואה לסוכן שרק מקבל פרומפט חלקי ועובד עצמאית.

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

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

רוצים לראות Spec Driven Development עם Agent OS בפעולה? הצטרפו אליי לזום בחמישי בבוקר וננסה לכתוב איתו מערכת לא טריוויאלית או לפחות פיצ'ר או שניים ממנה. מצטרפים בדף הוובינרים בקישור הזה:

https://www.tocode.co.il/talking_ai

לימודי תכנות עם AI - מה יותר קל, מה יותר קשה ולאן זה הולך

22/12/2025

חבר שואל - אני רוצה ללמוד תכנות אבל לא בטוח אם יש טעם. אני רוצה לעבוד בפיתוח אבל כל מה שאני רוצה לכתוב ה AI כותב טוב יותר. מי צריך אותי שם בכלל?

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

עכשיו מה זה אומר עלינו?

המשך קריאה

פתרון AoC 2025 יום 5 חידת הטווחים

21/12/2025

אתמול כתבתי על בדיקת cover? של רובי והיום נראה איך היא עוזרת לנו לפתור בעיה אמיתית מתוך Advent Of Code האחרון ואני מתכוון לחלק השני של יום 5. מה שאהבתי בחלק הזה היה שכשניסיתי לקחת את הפתרון של חלק 1 ולהרחיב אותו לחלק 2 זה פשוט לא עבד וכך הבנתי את הטעות שהיתה לי בחלק 1. אבל בואו לא נקדים את המאוחר ונלך לראות את התרגיל.

המשך קריאה

היום למדתי: בדיקת שייכות ל Range ב Ruby

20/12/2025

תמיד השתמשתי ב include? כדי לבדוק אם מספר נמצא בטווח מסוים והקוד הזה עובד ודי יעיל:

3.3.5 :007 > (1..10).include?(5)
 => true

רובי לא מתאמץ לעבור איבר-איבר בטווח מאחר וזה טווח של מספרים שלמים ו-5 הוא מספר שלם אז מספיק לבדוק שהוא גדול מההתחלה וקטן מהסוף. אגב קל לראות את זה כשמחפשים שבר בתוך הטווח:

3.3.5 :010 > (1..10).include?(4.5)
 => true

כשברור שאם ננסה לעבור איבר-איבר לא נמצא את ארבע וחצי:

3.3.5 :011 > (1..10).to_a.include?(4.5)
 => false

השבוע נתקלתי במקרה בפונקציה conver? שנראתה כאילו עושה אותו דבר:

3.3.5 :008 > (1..10).cover?(5)
 => true
3.3.5 :009 > (1..10).cover?(4.5)
=> true

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

3.3.5 :013 > ('a'..'z').class
 => Range

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

3.3.5 :014 > ('a'..'z').include?('t')
 => true
3.3.5 :015 > ('a'..'z').cover?('t')
 => true

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

3.3.5 :018 > ('a'..'d').to_a
 => ["a", "b", "c", "d"]
3.3.5 :019 > ('a'..'d').include?('bob')
 => false
3.3.5 :020 > ('a'..'d').cover?('bob')
 => true

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

איך אפשר לפרוץ למישהו לטלגרם ולמה חשוב לשים לב

19/12/2025

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

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

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

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

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

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

  6. פישינג והנדסה חברתית - קיבלת לינק בטלגרם שאתה מאוד רוצה לפתוח, אתה לוחץ עליו ומגיע למסך שנראה כמו מסך הכניסה של טלגרם ווב אבל מזויף. אתה מתחבר במסך זה ו-בום, התוקף קיבל גישה לחשבון שלך. טלגרם וווטסאפ מאפשרות לנו לסרוק קוד QR כדי להתחבר, כלומר אנחנו נכנסים לאתר טלגרם, שם יש קוד QR ומבקשים מאתנו לפתוח את הטלגרם בטלפון ולסרוק את הקוד כדי להתחבר. עכשיו נניח שבמקום להגיע לאתר טלגרם המקורי הגעתם לאתר טלגרם ווב מזויף. אתם מנסים להתחבר דרך סריקת הקוד אבל למעשה אתם סורקים קוד שהתוקף יצר ואחרי שתסרקו אותו אתם תחברו את התוקף לטלגרם שלכם. או - נכנסתם לאתר כדי להתחבר לטלגרם ווב, הם כותבים שהם שלחו קוד לטלגרם בטלפון שלכם ואתם מכניסים את הקוד באתר אבל בעצם הייתם באתר מתחזה והקוד שלכם הגיע לתוקף שמשתמש בו כדי להתחבר בשמכם לטלגרם. אם הסיפור של בנט נכון ורק הטלגרם נפרץ אז התרחיש הזה הוא התרחיש הסביר ביותר.

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

תשעת אלפים בדיקות עברו

18/12/2025

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

https://github.com/EmilStenstrom/justhtml/

והקוד של סיימון ב JavaScript נמצא כאן:

https://github.com/simonw/justjshtml/

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

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

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

class _DummyNode:
    namespace: str = "html"


class StreamSink:
    """A sink that buffers tokens for the stream API."""

    tokens: list[StreamEvent]
    open_elements: list[_DummyNode]

    def __init__(self) -> None:
        self.tokens = []
        self.open_elements = []  # Required by tokenizer for rawtext checks

    def process_token(self, token: Tag | CommentToken | DoctypeToken | Any) -> int:
        # Tokenizer reuses token objects, so we must copy data
        if isinstance(token, Tag):
            # Copy tag data
            self.tokens.append(
                (
                    "start" if token.kind == Tag.START else "end",
                    (token.name, token.attrs.copy()) if token.kind == Tag.START else token.name,
                )
            )
            # Maintain open_elements stack for tokenizer's rawtext checks
            if token.kind == Tag.START:
                # We need a dummy object with namespace for tokenizer checks
                # Tokenizer checks: stack[-1].namespace
                # We can just use a simple object
                self.open_elements.append(_DummyNode())
            else:  # Tag.END
                if self.open_elements:
                    self.open_elements.pop()
                # If open_elements is empty, we ignore the end tag for rawtext tracking purposes
                # (it's an unmatched end tag at the root level)

        elif isinstance(token, CommentToken):
            self.tokens.append(("comment", token.data))

        elif isinstance(token, DoctypeToken):
            dt = token.doctype
            self.tokens.append(("doctype", (dt.name, dt.public_id, dt.system_id)))

        return 0  # TokenSinkResult.Continue

    def process_characters(self, data: str) -> None:
        """Handle character data from tokenizer."""
        self.tokens.append(("text", data))

ובגרסת ה JavaScript נראה כך:

class StreamSink {
  constructor() {
    this.events = [];
    this.openElements = [{ namespace: "html" }];
  }

  processToken(token) {
    if (token instanceof Tag) {
      if (token.kind === Tag.START) {
        this.events.push(["start", [token.name, { ...(token.attrs || {}) }]]);
      } else {
        this.events.push(["end", token.name]);
      }
      return TokenSinkResult.Continue;
    }

    if (token instanceof CommentToken) {
      this.events.push(["comment", token.data]);
      return TokenSinkResult.Continue;
    }

    if (token instanceof DoctypeToken) {
      const dt = token.doctype;
      this.events.push(["doctype", [dt?.name ?? null, dt?.publicId ?? null, dt?.systemId ?? null]]);
      return TokenSinkResult.Continue;
    }

    return TokenSinkResult.Continue;
  }

  processCharacters(data) {
    this.events.push(["text", data]);
  }
}

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

this.events.push(["start", [token.name, { ...(token.attrs || {}) }]]);
      } else {

יותר מזה גם את הלוגיקה של open_elements ה AI מחק לגמרי וכעת משתנה זה נשאר בתור משתנה של המחלקה בלי שימוש אמיתי שם.

אבל הבדיקות עברו...

אני שומע אתכם. סט של 9,200 בדיקות חייב לשכנע אותנו שהספריה תואמת "100%" מבחינת התנהגות לספריה המקורית. וזה בדיוק מה שהופך את הדוגמה הזאת למוצלחת כדי שנשים לב שקוד הוא לא רק התוצאה שלו. ההערות, ההכנה שאנחנו בונים פנימה לשינויים עתידיים, אפילו הבחירה בשמות מסוימים ובסגנון כתיבה מסוים, כל אלה משפיעים על איכות המערכת. משפיעים על היכולת לתחזק, לשפר ולהרחיב אותה מחר ובהמשך.

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

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

"לא חשבתי על זה" כבר לא אופציה

17/12/2025

אני מסתכל שוב על החלק הראשון של יום 2 של Advent Of Code האחרון. התרגיל היה למצוא מספרים שהחצי השמאלי שלהם שווה לחצי הימני למשל 1313 או 123123. אני פתרתי את זה ברובי באמצעות ריצה על כל המספרים בטווח ובדיקת מחרוזות:

ids[0...ids.length / 2] == ids[ids.length / 2...]

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

n.to_s + n.to_s

ג'מיני ראה עוד צעד קדימה והבין שכל מספר מהצורה שרציתי בעצם מתחלק בעשר בחזקת חצי ממספר הספרות ועוד אחד כלומר כל מספר בן 4 ספרות מהצורה שאנחנו צריכים יתחלק ב 101, כל מספר בן 6 ספרות יתחלק ב 1001 וכך הלאה. לב הלולאה אצל ג'מיני היה בסך הכל:

while curr <= end:
    print(f"Found {curr}")
    total += curr
    curr += basis

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

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

תכנות הוא כבר לא היכולת לחשוב על הפתרון הכי נכון הכי מהר.

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

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

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