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

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

עולם ללא קוד

20/02/2026

כבר היום אני לא בטוח שכדאי לאנשים ללמוד את החשיבות של HTML סמנטי, את ההבדלים בין section ל article ומתי להשתמש ב nav במקום ב div. אם זה חשוב אנחנו מצפים ש AI ישתמש בזה כשנבקש ממנו לכתוב HTML.

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

מה לגבי node.js ואקספרס? או ראסט? או Java?

מה שמיוחד ב HTML-ים וב Shell Scripts הוא שהממשק שלהם פשוט. אפשר להוסיף עוד מהם בלי להכיר את הקיימים. קובץ HTML גרוע יכול לשבור רק את עצמו. סקריפט גרוע ישבור רק את עצמו.

הנה מה שכתב rupayanc ברדיט בנוגע ל PR-ים שהוא מקבל על פרויקטי קוד פתוח שלו:

The other half compile but introduce subtle bugs because the model doesn't understand the actual design constraints of the project.

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

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

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

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

כשהקוד הופך זול מאוד אין צורך להבין תחביר. לא צריך לדעת לקרוא מבנה של List Comprehension בפייתון או מה ההבדל בין פונקציה אנונימית לפונקציית חץ ב JavaScript.

אני מבין מה עשית שם

19/02/2026

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

הנה רובי:

def create_connection
  Bunny.new(@connection_config[:url]).tap(&:start)
end

הפונקציה פותחת חיבור ל Rabbitmq ומחזירה את החיבור שנפתח. נתעלם רגע מהשאלה אם צריך פונקציה בשביל זה (אני חושב שלא), אבל השאלה היותר חשובה היא מה ה tap עושה שם? הנה הקוד בצורה הכי מפורשת שלו:

def create_connection
  conn = Bunny.new(@connection_config[:url])
  conn.start
  return conn
end

עכשיו זה יותר ברור, קלוד רצה להפעיל start וגם להחזיר את אוביקט ה connection והכל בשורה אחת. הוא הסתבך בגלל שאם היה כותב:

def create_connection
  Bunny.new(@connection_config[:url]).start
end

אז הפונקציה היתה מחזירה את מה ש start מחזירה ואולי זה משהו אחר ממה ש new מחזירה. פה המקום לשים לב לנטיה של AI לעבוד בתבניות - קלוד לא בדק מה start מחזירה. אם הוא היה בודק הוא היה רואה שהיא מחזירה בדיוק את אוביקט החיבור כי מי שכתב את Bunny כבר חשב על מנגנון הקריאה הזה. כלומר ה tap מיותר לגמרי שם.

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

ואז ניסיתי לשכנע את ChatGPT לכתוב קוד יעיל

18/02/2026

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

write a python function that takes 2 arrays and returns the items from first array that are not in the second array (array diff)

וקיבלתי אחלה פתרון:

def array_diff(arr1, arr2):
    """
    Returns items from arr1 that are not in arr2.
    Preserves order and duplicates from arr1.
    """
    set2 = set(arr2)  # Convert to set for faster lookups (O(1))
    return [item for item in arr1 if item not in set2]

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

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

I think for small arrays it will be faster to just run the nested loop

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

now combine the two - if the input is small use nested loop and if it's large use the set

וכמובן AI כמו AI מצליח לקחת כל רעיון טוב ולשבור אותו, הנה הקוד שהוא יצר:

def array_diff(arr1, arr2, threshold=50):
    """
    Returns items from arr1 that are not in arr2.

    - Uses nested lookup for small inputs.
    - Uses set-based lookup for larger inputs.
    - Preserves order and duplicates from arr1.

    threshold: size of arr2 below which nested lookup is used.
    """

    # If arr2 is small, avoid set overhead
    if len(arr2) <= threshold:
        result = []
        for item in arr1:
            if item not in arr2:
                result.append(item)
        return result

    # Otherwise use set for faster lookups
    set_arr2 = set(arr2)
    return [item for item in arr1 if item not in set_arr2]

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

היום למדתי: Temporal נכנס לעשות סדר בתאריכים ב JavaScript

17/02/2026

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

קודם כל בשביל לדעת מה הזמן עכשיו נוכל להפעיל:

Temporal.Now.instant()

ולקבל אוביקט עם epochMilliseconds ו epochNanoseconds. מחרוזת של תאריך ושעה אנחנו מקבלים מ:

Temporal.Now.instant().toString()

בפורמט:

'2026-02-16T13:21:55.963Z'

ואם אנחנו צריכים אזור זמן אחר אפשר להמיר:

Temporal.Now.instant().toZonedDateTimeISO('UTC').toString()
15:25:11.803 '2026-02-16T13:25:11.801+00:00[UTC]'
15:25:13.784 Temporal.Now.instant().toZonedDateTimeISO('Europe/Paris').toString()
15:25:13.788 '2026-02-16T14:25:13.785+01:00[Europe/Paris]'
15:25:15.889 Temporal.Now.instant().toZonedDateTimeISO('America/New_York').toString()
15:25:15.892 '2026-02-16T08:25:15.89-05:00[America/New_York]'
15:25:21.326 Temporal.Now.instant().toZonedDateTimeISO('Asia/Jerusalem').toString()
15:25:21.337 '2026-02-16T15:25:21.328+02:00[Asia/Jerusalem]'

אפשר גם להוסיף או להפחית זמנים למשל:

Temporal.Now.plainDateISO().add({days: 2}).toString()
Temporal.Now.plainDateISO().subtract({days: 2}).toString()

Temporal.Now.plainTimeISO().add({seconds: 30})

דף התיעוד עמוס בפיצ'רים ושווה להעיף מבט אם אתם מחפשים להיפרד מ moments או days בקוד חדש שכותבים:

https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Temporal

מתנות חינם

16/02/2026

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

בעולם של ספריות לפיתוח סוכנים חכמים המשחק שונה לגמרי: הממשק מול המודל מתפתח כל הזמן, אגרגטורים כמו Bedrock, Open Router ו Copilot Models עוטפים את ה API הבסיסי ויכולים לספק פונקציונאליות נוספת או כפולה, סביבות להרצת סוכנים כמו Vertex AI Engine או Agent Core מספקות עוד יכולות וכל אלה משתנים כל הזמן. כך יש לנו היום כלים מובנים בתוך ה API של OpenAI, כלים מובנים ב AgentCore, שער של אייג'נט קור להרצת כלים או חיבור שרתי MCP וספריית פיתוח סוכנים שבעצמה מתחברת לשרתי MCP.

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

ומתוך הבנה של תנאי העבודה השונים כדאי לאמץ גישה שונה למתנות שאנחנו מקבלים מכל עולם:

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

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

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

השאלה היא לא כמה אחוז מהקוד ה AI כותב

15/02/2026

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

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

הנה כמה מטריקות טובות יותר לשילוב AI במהלך הפיתוח:

  1. כמה אחוז מהקוד נכנס למערכת בלי שאף אחד קרא אותו?
  2. כמה אחוז מהקוד ש AI כותב הרגשנו בנוח לזרוק לפח?
  3. מה אנחנו עושים היום טוב יותר או אחרת ממה שעשינו בעבר בזכות ה AI?
  4. כמה גרסאות שונות אני בודק לכל פיצ'ר לפני שאני מתקדם?
  5. כמה תהליכי עבודה מבוססי AI חדשים בנינו בארגון?

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

אם ה AI כתב 100% או 80% מהקוד זה לא הדבר החשוב, מה שחשוב זה איך אנחנו עובדים היום ואיך תהליך העבודה השתפר בזכות שימוש בכלי AI.

הגדרת תלויות ב docker-compose עלולה להסתיר באג במערכת

14/02/2026

נתבונן בדוקר קומפוז הבא:

services:
  db:
    image: postgres:15
    container_name: my_postgres

  api:
    build: ./api
    container_name: my_api
    depends_on:
      - db

קלאסי נכון? שרת API שתלוי בבסיס נתונים והגדרת depends_on בדוקר קומפוז כדי לוודא שבסיס הנתונים עולה קודם.

אבל בעצם... למי אכפת מה עולה קודם?

אם שרת ה API מנסה לעשות משהו עם בסיס הנתונים ולא מצליח הוא צריך להציג שגיאה. כשבסיס הנתונים יעלה ה API יחזור לעבוד. מה עוזר ה depends_on כאן?

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

איך לשמור שיחות ב LangGraph

13/02/2026

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

from langgraph.graph import StateGraph, MessagesState, START, END
from google.colab import userdata

from langchain_google_genai import ChatGoogleGenerativeAI
from langchain_core.messages import HumanMessage

# --- setup ---
api_key = userdata.get("GEMINI_API_KEY")

llm = ChatGoogleGenerativeAI(
    model="gemini-2.5-flash",
    temperature=1.0,
    google_api_key=api_key,
)

class GraphState(MessagesState):
    pass


# --- graph node ---
async def chat_node(state: GraphState):
    response = await llm.ainvoke(state["messages"])
    return {
        "messages": [response]
    }


# --- build graph ---
graph = StateGraph(GraphState)
graph.add_node("chat", chat_node)

graph.add_edge(START, "chat")
graph.add_edge("chat", END)

app = graph.compile()


# --- interactive loop (Colab-safe) ---
state: GraphState = {"messages": []}

try:
    while True:
        user_input = input("You: ").strip()

        state = await app.ainvoke(
            {"messages": [HumanMessage(content=user_input)]},
            state
        )

        print("AI:", state["messages"][-1].content)

except KeyboardInterrupt:
    print("\nExiting chat. Goodbye!")

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

המשך קריאה

השוואה בין Claude Code, Copilot CLI ו Cursor CLI

12/02/2026

סוכני הקידוד משורת הפקודה השתפרו מאוד ממש בחודשים האחרונים. זה התחיל עם קלוד קוד וההתלהבות הגדולה ממנו, משך את תשומת הלב של מייקרוסופט והביא לפיתוח Cursor CLI ואחרונים הגיעו Cursor עם כלי CLI משלהם. כל השלושה מעולים ובזכותם הצלחתי בתקופה האחרונה לחזור לקודד עם vim. בכל זאת אני רוצה לשתף את המסקנות המרכזיות שלי מהעבודה עם שלושתם, לא בשביל להגיד תשתמשו בכלי X או Y אלא יותר לעשות סדר, לראות חוזקות וחולשות ולתת לכם את הכלים איך לבחור.

המשך קריאה

מתי בכל זאת כדאי לכתוב קוד בלי סוכן קידוד

11/02/2026

זוכרים את כל אלה שמסבירים שאנחנו צריכים לתת לסוכן קידוד לכתוב 100% מהקוד או שאצלם סוכני קידוד כותבים 80% או 99% מהקוד? אז השאלה שהייתי רוצה לשאול אותם היא איזה קוד הם בכל זאת כותבים לבד בלי סוכן קידוד. מה זה האחוז הנותר או ה 20% הנותרים.

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

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

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

cleanStaleFiles()
cleanStaleFiles()

אולי הוא אפילו ייתן איזו הערה שמסבירה שהפונקציה מנקה עד 20 קבצים כל הפעלה ובעולם האמיתי היא מופעלת מ cron job ולכן זה לא בעיה אבל אנחנו פה בקוד של בדיקה ושם יכולים להיות גם 30 קבצים שצריכים ניקוי אז פעמיים מבטיחות לו שיימחקו עד 40 קבצים. ב Code Review של מאות שורות זה משהו שאני בקלות יכול לסלוח עליו.

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

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