es
Feedback
ToCode

ToCode

Ir al canal en Telegram

טיפים קצרים למתכנתים מאת ינון פרק

Mostrar más
1 419
Suscriptores
-124 horas
Sin datos7 días
-430 días
Archivo de publicaciones
ToCode
1 419
למי קראת מתקדם? בואו נדבר על "מתקדם". רוב המתכנתים שמסתכלים על ספריה בפעם הראשונה מחפשים את ה Getting Started ואת ה Tutorials (והיום אפילו זה לא, רק נותנים לקלוד לעבוד). ואז אנחנו פותחים דף תיעוד של ספריה כמו vue-i18n ויכולים מהר מאוד להבין איך להשתמש בספריה לפי התבנית הבאה:
const i18n = createI18n({
  locale: 'ja',
  fallbackLocale: 'en',
  messages: {
    en: {
      message: {
        hello: 'hello world'
      }
    },
    ja: {
      message: {
        hello: 'こんにちは、世界'
      }
    }
  }
})
אז הלכתי לקלוד, ג'מיני ו ChatGPT וביקשתי מכל אחד מהם לכתוב דמו קצר שמראה איך לשלב את vue-i18n ביישום שלי. התוצאה? כולם כתבו קוד שטוען את כל ההודעות בכל השפות בצורה סטטית לתוך קובץ ה JS כלומר מכניס את כל הטקסטים של כל השפות לבאנדל. ברור למה זה קורה - אם המדריך Lazy Loading נמצא בחלק "המתקדם" של התיעוד אף אחד לא יקרא אותו וה AI יתייחס אליו פחות ברצינות. רוב קוד הדוגמה שמוצאים באינטרנט יטען סטטית את כל הטקסטים וזה גם יהיה ה Boilerplate ש AI מייצר. אבל חשוב להגיד, טעינה דינמית של הטקסטים שאנחנו צריכים לפי השפה זה לא מנגנון מתקדם אלא חלק בסיסי ומהותי מהתמיכה בריבוי שפות. ריבוי שפות לא יכול לבוא על חשבון ביצועים וזה ברור לכל מי שכתב מערכת כזו. המשימה שלנו בכתיבת קוד ותיעוד, ובמיוחד בעידן ה AI, היא לכתוב את ה Best Practices שיהיו בעצם ה Only Practices ויהיו במקום בולט במערכת. כמו שאומרים בזן של פייתון "צריכה להיות דרך ברורה (ועדיף רק אחת) לעשות דברים".

ToCode
1 419
values = banks.map {|b| iterate_bank(b, 12) }
    puts values.sum
  end

  def iterate_bank(bank, num_batteries)
    value = 0
    next_digit_index = 0

    num_batteries.times do |i|
      options = bank[next_digit_index..-(num_batteries - i)]
      next_digit = options.max
      next_digit_index += options.index(next_digit) + 1
      value += next_digit * (10 ** (num_batteries - 1 - i))
    end

    value
  end
end

if $PROGRAM_NAME == __FILE__
  d = Day3.new
  d.parse("input.txt")
  d.part1
  d.part2
end

ToCode
1 419
שלושה פתרונות ראשונים ל Advent Of Code 2025 עוד שנה נגמרת וכמו בכל דצמבר אריק ווסטל מפרסם שוב סט חידות תכנות חדש באתר Advent Of Code. הפעם מספר החידות ירד ל 12, בעיקר כי הוא קצת התעייף אבל אני חייב להודות שכמו עם העוגות של שמו, גם פה ההקטנה היא לטובה. כבר כמה שנים שלא הצלחתי למצוא זמן לסיים את כל הסט ואולי הפעם עם 12 חידות אוכל למצוא את המוטיבציה והזמן להתמיד. בינתיים אני לא בקצב ואני מסיים את השבוע הראשון עם 3 חידות פתורות מתוך 5 שפורסמו. תכף החידות והפתרונות שלי אבל קודם הבהרה - אני מפרסם את הפתרונות האלה לא בתור פתרונות בית ספר שצריך ללמוד מהם (את זה אפשר לקבל מ ChatGPT שאלוף בחידות מהסוג הזה) אלא כדי לתת גם לכם מוטיבציה לנסות ולפתור. זה אולי לא מושלם, לא מספיק מהיר וקצת מסורבל אבל גם זה חלק מהקסם של לפתור לבד ולהתבלבל באינדקסים. בואו נראה את הקוד. יום 1 - הכספת התרגיל כאן: https://adventofcode.com/2025/day/1 מאיזושהי סיבה כשהתחלתי את המשחק חשבתי שפייתון היא השפה המושלמת אז פתרתי בה. אל דאגה מהר מאוד חזרתי לרובי. יום ראשון חלק 1:
def move_dial(old_value, line):
    count = int(line[1:])

    if line.startswith("L"):
        return (old_value - count) % 100
    elif line.startswith("R"):
        return (old_value + count) % 100
    else:
        raise Exception(f"Line {line}")

dial = 50
zero_count = 0

with open('input.txt', encoding='utf-8') as f:
    for line in f:
        dial = move_dial(dial, line)
        if dial == 0:
            zero_count += 1

print(f"{zero_count=}")
חלק 2 - ארוך מדי וטעויות טפשיות בחשבון אבל בסוף זה עבד:
def dial_diff(line):
    count = int(line[1:])

    if line.startswith("L"):
        return -count
    elif line.startswith("R"):
        return count
    else:
        raise Exception(f"Line {line}")

dial = 50
zero_count = 0

with open('input.txt', encoding='utf-8') as f:
    for line in f:
        count = dial_diff(line)
        full_rounds = abs(count) // 100

        if count > 0:
            # move right
            next_dial = (dial + count) % 100
            if (next_dial < dial and next_dial != 0):
                full_rounds += 1

        elif count < 0:
            next_dial = (dial + count) % 100
            if (next_dial > dial and next_dial != 0 and dial != 0):
                full_rounds += 1

        dial = next_dial
        zero_count += full_rounds
        if dial == 0:
            zero_count += 1

print(zero_count)
יום 2 - חנות מתנות התרגיל כאן https://adventofcode.com/2025/day/2 חלק ראשון חזרתי לרובי וכבר נשמתי לרווחה. את החלק הראשון עוד פתרתי בלי ביטויים רגולאריים אבל בחלק השני קיצרתי את החישובים ונתתי למנוע הביטויים הרגולאריים לעבוד בשבילי:
class Aoc2025Day2
  def initialize(input_file_name)
    @ranges = parse(input_file_name)
  end

  def parse(input_file_name)
    File
      .read(input_file_name)
      .split(',')
      .map {|r| r.split('-') }
      .map {|a, b| a.to_i..b.to_i }
  end

  def part1
    @ranges.flat_map {|r| r.filter {|id| valid?(id) } }.sum
  end

  def part2
    @ranges.flat_map {|r| r.filter {|id| id.to_s.match(/^(\w+)\1+$/) } }.sum
  end

  def valid?(id)
    ids = id.to_s
    ids[0...ids.length / 2] == ids[ids.length / 2...]
  end
end

if __FILE__ == $PROGRAM_NAME
  a = Aoc2025Day2.new("input.txt")
  puts a.part1
  puts a.part2
end
יום 3 - לובי התרגיל כאן https://adventofcode.com/2025/day/3 הייתי שמח לכתוב פתרון יותר יעיל שסורק את המערך רק פעם אחת אבל index ו max כבר בנויים ברובי ואצל מתכנתים העצלות לפעמים מנצחת. לפחות הצלחתי לחתוך את המחרוזת בלי להתבלבל יותר מדי באינדקסים. זה הקוד:
class Day3
  attr_accessor :banks

  def parse(input_file_name)
    @banks = File
      .read(input_file_name)
      .lines
      .map {|l| l.strip.split('') }
      .map {|bank| bank.map(&:to_i) }
  end

  def part1
    values = banks.map {|b| iterate_bank(b, 2) }
    puts values.sum
  end

  def part2

ToCode
1 419
דברים שאני עושה היום אחרת בזכות ה AI בעקבות כמה פוסטים שקראתי בזמן האחרון על השחיקה במיומנות של מתכנתים ואיך כולנו הופכים לזומבים חשוב להגיד - מפתחים שמשתמשים נכון ב AI עובדים יותר מהר, לומדים יותר מהר, מדלוורים קוד טוב יותר בפחות זמן ויודעים יותר על הקוד של המערכות שלהם. הנה רשימה חלקית של דברים שאני עושה היום אחרת בזכות ה AI ופעם יכלתי רק לחלום עליהם: 1. מסתכל על כמה מימושים כדי להבין את הבעיה - בוובינר הבוקר על סוכני קידוד בענן הראיתי איך אני יכול לקחת רעיון ולהפוך אותו לקוד עובד במערכת שלי, ואיך עם עוד 2 לחיצות אני יכול לקבל עוד גרסאות ומימושים שונים לאותו רעיון. היום כשאני צריך לפתח פיצ'ר אני כבר לא צריך לחשוב מראש על כל מה שהולך להשבר, אני פשוט נותן ל AI לרוץ ולשבור דברים (על מכונה שלו. בענן.). אני משקיע המון זמן בלקרוא את המימושים וכמעט אף פעם לא לוקח את הקוד אליי. מה שכן קורה זה שאני לומד מהם על ארכיטקטורה. אני לומד מה היה קשה ל AI לבנות, איפה הוא הסתבך ויצר פתרונות עקומים ולמה הוא הסתבך שם. קריאת מימושי AI לפיצ'ר חדש מלמדת אותי המון על המערכת ומבנה הקוד. מה ברור בקוד שלי, מה לא מספיק ברור. איזה אבסטרקציות חסרות. איפה האבסטרקציות שיצרתי מעודדות בעיות ביצועים או בעיות אבטחה. פעם הייתי חושב על רעיון למבנה מחלקות במערכת ורק אחרי כמה חודשים מקבל מספיק פידבק כדי להבין אם המבנה הזה טוב. היום באותו שבוע אני יכול לנסות 4 היררכיות ירושה שונות ולראות איך אוסף של פיצ'רים יכול להבנות על כל היררכיה. 2. מרפד כל פונקציה בבדיקות יחידה לפני שמתחיל לשנות אותה - זה לא היה פרקטי לפני כמה שנים ובטח לא שווה את ההשקעה. היום כשאני ניגש לעבוד על פונקציה אני מתחיל עם פרומפט "תסביר מה עושה הפונקציה", לוקח את ההסבר שלו וממשיך עם פרומפט "בנה תוכנית בדיקות לפונקציה כולל מקרי קצה" ואז "צא לדרך וממש את כל הבדיקות". כשיש מספיק דוגמאות מסביב בקוד ותשתית טובה ה AI מצליח לבנות תוכנית של 30-40 בדיקות לפונקציה בודדת וכולן עוברות בריצה ראשונה כך שזה לא לוקח לי זמן פיתוח ואני יכול להמשיך לארגן מחדש את הקוד בבטחון מלא שלא שברתי כלום, או אם כן התכוונתי לשבור אז אני יודע ששברתי רק את מה שתכננתי. 3. זורק קוד - תמיד אהבתי למחוק קוד אבל עכשיו עם AI אני אפילו הרבה פחות קשור אליו ממה שהייתי קודם. מימוש לא טוב? משהו לא מספיק ברור? לא נראה יפה? git restore ומנסים שוב. הקוד הספציפי איבד מחשיבותו. מימוש של פונקציה מיוצר בלחיצת כפתור. הקסם הוא בממשקים, בחיבורים, בתיעוד, במבנה הגדול יותר של המערכת. 4. לומד על כל שורה - פעם כשראיתי שורת קוד מוזרה חיפשתי בתיעוד מה היא עושה אבל הרבה פעמים לא הבנתי עד הסוף למה היא שם ומתי עוד אפשר או אי אפשר להשתמש במבנה דומה. היום החיפוש בתיעוד הוא מתוך ה IDE ומכיל את שורת הקוד המוזרה יחד עם כל הקונטקסט. ההסבר שאני מקבל (ושאלות ההמשך שאני מעלה) עוזר לי להבין הרבה יותר מהר והרבה יותר טוב למה שורה מסוימת כתובה כך. הלימוד דרך דיאלוג עם AI על כל שורה מאפשר לראות דברים שפעם הייתי בקלות מפספס. הלימוד המהיר והדיאלוגי וקבלת הסברים מדויקים על סעיפים בתיעוד שאני לא מבין מאפשר לי לראות דברים שפעם היה לי קשה לראות. במחשבה ש"הפרויקט שלי מסובך מדי בשביל AI" יש גרעין של אמת, אבל כדאי לחדד - הפרויקט שלי מסובך מדי בשביל ש AI יוכל לבנות בו פיצ'ר מאפס (וכנראה גם מסובך מדי בשביל שאני אכתוב בו פיצ'רים מאפס), אבל בעזרת AI אני יכול להתמקד בחשיבה ובבניית תשתית שתהפוך אותו להרבה פחות מסובך. אבל יותר חשוב, מפתחים שעובדים עם AI בצורה נכונה יכולים להתמקד בדברים שתמיד היו בליבת המקצוע: פיתוח אבסטרקציות, המצאת פתרונות יצירתיים לבעיות, זיהוי בעיות לפני שהן קורות ומניעתן, חיפוש אחר דרכים טובות יותר לבנות מערכות.

ToCode
1 419
למי צלצלו הפעמונים שלוש שאלות שאנחנו אוהבים להשאיר פתוחות וחבל: מי אחראי להקשיב לאזעקה כשדברים נשברים? מי מתעדף את התיקון? ואיזה דברים צריך בכלל לתקן? 1. מי אחראי להקשיב לאזעקה - של מי התפקיד לשים לב כשהגיבוי נכשל? כשסקריפט הבניה מזהיר שקבצי ה JS שהוא יצר גדולים מדי? שהקוד כולל יותר מדי חלקים דומים במקום תשתית משותפת? שהשרת מתחיל לעבוד יותר קשה ממה שאנחנו רגילים? "כל אחד יכול לראות את זה" בדרך כלל מוביל לזה שאף אחד לא שם לב. 2. מי מתעדף את התיקון - צריך להגיע לפרודקט בשביל להכניס פיצ'ר "הקטנת JavaScript"? זה עוצר ספרינט? נתפס ב Code Review? ואם שמתי לב אחרי המיזוג? 3. מה בכלל צריך לתקן - מתקנים כל מה שזורק Warning? רק אם ה Warning ממש מפחיד? רק אם יש זמן? יש היום המון מנגנונים אוטומטיים שצועקים עלינו כשהקוד מזייף וככל שיש יותר מהם כך אנחנו מפתחים המון מנגנונים שמאפשרים להתעלם מאותם מנגנונים אוטומטיים. שמים Husky? תוך רגע אנחנו מגלים איך לבטל אותו. חייבים לעבור בדיקות בשביל לדחוף גרסה? נשים את הבדיקה ב skip רק לגרסה הזו. אנחנו מתעלמים מהאזהרות כי הרבה מהן הן False Positives. מתוך 3 הודעות שקיבלתי היום ב Code Review מ AI שלושתן היו טעויות של ה AI וכולן בזבזו לי זמן יקר לקרוא ולהבין למה ה AI התכוון ולמה הוא טועה. באותו זמן קובץ ה JavaScript במערכת כבר עבר את ה 5 מגה ואף אחד כבר לא זוכר מתי האזהרה על זה התחילה להופיע. כמה רעיונות שיכולים לעזור: 1. אנחנו חייבים להחליט איזה בעיות מספיק חשובות בשביל לעצור את העבודה ואת כל השאר לכבות. 2. לכל תחום בפרויקט צריך להיות בן אדם שאחראי להסתכל על האזהרות. הוא או היא יקבעו מתי האזהרות יקפצו והם יהיו אחראים לצעוק כשיש בעיה שדורשת את זה, לפני שהמצב יחמיר. 3. הוק לפני קומיט זה לא רעיון טוב כי הוא לא מגיע לבן אדם הנכון בזמן הנכון. המפתח שעכשיו דוחף גרסה עם תלות ישנה צריך את זה לדמו של שבוע הבא וממילא יש משימה של שדרוג תלויות בספרינט הבא. עדיף שאחראי אבטחת מידע יקבל Push עם הפרטים ויחליט מה לעשות עם זה ואם באמת יש סכנה. המטרה שלנו פשוטה - לפתח יותר מהר ועם פחות טעויות. מערכות תוכנה שצועקות על כולם על כל שטות לא עוזרות.

ToCode
1 419
קוד שנראה טוב אבל הוא בעצם לא (היוש AI) בוובינר השבוע אראה איך לעבוד עם סוכני AI בענן. הם מדהימים ויכולים לעשות דברים מאוד יצירתיים אבל חופש הפעולה שלהם עובד הרבה פעמים גם נגדנו. שואלים אותי לפעמים בוובינרים אם צריך לקרוא כל שורה ש AI כותב ואני חושב שהתשובה מורכבת. מצד אחד אי אפשר להכנס "לראש של AI" כי זה מכונה וקל מאוד לפספס דברים קטנים שהוא עושה לא בסדר. מצד שני רוב הפעמים ה AI משאיר סימנים לטעויות שלו, סימנים שאנחנו יכולים לראות בקוד ש AI כותב ושעוזרים לנו להיות יותר חשדנים לגבי התוצאה. דוגמה? ברור בשביל זה באנו. כאן יש PR לפיצ'ר שביקשתי מקופיילוט לכתוב על פרויקט קוד פתוח שלי שנקרא לנגלטס. ביקשתי שיבנה מסך נגן שיראה שיר מיוטיוב ולידו את המילים, התרגום, טבלה עם אוצר מילים מהשיר ואפשרות להוריד את אוצר המלים לקובץ CSV: https://github.com/ynonp/langlets-rails/pull/71 הוא עשה עבודה מדהימה - מתיאור של כמה מילים הוא יצר לי מסכים עובדים בתוך הפרויקט. אני יכול למשוך את ה PR ולראות את זה לייב על המחשב שלי. למתכנת אנושי זה היה לוקח יותר זמן. לי, שאני מכיר ריילס טוב ומכיר את הפרויקט טוב זה היה לוקח יותר זמן. חלק מזה זה באמת הכח של AI לכתוב קוד מהר וחלק אחר מזה זה חוסר היכולת של AI לקבל החלטות טובות. בואו נקרא את ה PR לראות מה עובד ומה שבור שם. הצגת הנגן הקובץ הראשון שה AI כתב נקרא play_controller.rb. זה הקובץ שמטפל בהצגת הנגן וכבר פה יש לי בעיה עם השם. השם play_controller אולי מתאים ל URL שביקשתי אבל הוא לא שם אינפורמטיבי ולא ברור ממנו מה המטרה של הקונטרולר. שם טוב יותר היה StandalonePlayerController - ואכן זה השם שהוא יבחר לקובץ ה JavaScript המקביל בהמשך ה PR. תכף נגיע לזה. כבר אחרי שליפת הרשומה מבסיס הנתונים אנחנו רואים את הבעיה הראשונה: אם הרשומה לא נמצאה קופיילוט החליט לענות ב Redirect למסך הבית. אני לא ביקשתי את זה ונתיבים אחרים במערכת זורקים Exception במצב כזה. נשים לב שהרבה פעמים החלטות כאלה אפשר לשפר באמצעות יצירת קבצי "חוקים ל AI". הבלוק הבא הוא לדעתי האתגר הכי גדול שלנו בקוד ש AI יוצר ואני מדביק אותו כאן:
* Get the medium from the first lesson to access phrases *
first_lesson = @course.lessons.first
unless first_lesson
  redirect_to root_path, alert: "No lessons found for this course"
  return
end

@medium = first_lesson.medium
זה בדיוק קוד שנראה טוב אבל הוא בעצם לא. מצד אחד הוא מבין שבשביל להגיע מקורס לוידאו הוא צריך לעבור דרך lessons, לקחת את השעור הראשון ושם לגשת לשדה medium. הוא גם מבין שיכול להיות שלא יהיו שעורים בקורס ואז אי אפשר להציג את הנגן. הקוד עובד, מטפל בכל המקרים וגם ברור ולכן נראה טוב. אבל מפתחי ריילס יודעים שלא כדאי להעמיס לוגיקה על הקונטרולר כי אי אפשר להשתמש בה שוב במקומות אחרים במערכת ולכן מפתחי ריילס טובים היו מכניסים את הבלוק הזה לפונקציה חדשה של course. "אה, אני רואה שצריך להגיע מקורס לוידאו אז אני אכתוב לזה פונקציה לגישה קלה" זה משהו ש AI כמעט ולא אומר. דבר שני ששבור כאן זה השימוש ב @. בריילס אנחנו מסמנים ב @ משתנים שאנחנו רוצים לגשת אליהם מקוד התבנית ושישפיעו בהמשך על ה HTML שיווצר. למשתנה @medium ב PR אף אחד לא ניגש אחרי הקונטרולר ולכן נכון יותר לסמן אותו כמשתנה פנימי של הפונקציה. הפונקציה מסתיימת בשורות:
* Prepare vocabulary data - all token translations from the medium *
@vocabulary = @phrases.flat_map(&:token_translations).uniq { |tt| [tt.original_text.downcase, tt.translation.downcase] }
שהיו עובדות הרבה יותר טוב בתור מתודה חדשה של Medium או באיזשהו Service Object כך שאפשר היה לבדוק אותן. עם שם טוב לפונקציה גם לא היינו צריכים את ההערה. שמירת אוצר המלים כ CSV הפונקציה השניה שה AI בנה נקראת vocabulary_csv. גם פה בעזרת Rules File אפשר היה לשכנע אותו ליצור קונטרולר חדש בשביל ה CSV הזה עם שם טוב כמו StandalonePlayerCsvController אבל בסדר גם רוב המפתחים האנושיים שאני מכיר לא היו מוסיפים קובץ קונטרולר חדש רק בשביל "הורדה כ CSV". מה שיותר מטריד זה שהפונקציה השניה מכילה הרבה קוד שנמצא כבר בפונקציה הראשונה. ברור לגמרי למה ה AI עושה את זה ושסוכן AI לעתים נדירות מצליח לראות אבסטרקציות. זאת הסיבה שאנחנו חייבים לעבור על הקוד ולתקן. קובץ ה JavaScript

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

ToCode
1 419
commit a883c360dfae324a365d9bfb9ab881865a33f0c4 (HEAD -> main)
Merge: d55f747 c363960
Author: ynonp <ynonperek@gmail.com>
Date:   Sun Nov 30 13:56:36 2025 +0200

    Merge branch 'develop'

diff --git a/demo.py b/demo.py
remerge CONFLICT (content): Merge conflict in demo.py
index 96d7fe5..ba6d51e 100644
--- a/demo.py
+++ b/demo.py
@@ -1,10 +1,5 @@
-<<<<<<< d55f747 (fixed text)
-if __name__ == "__main__":
-    print("Hello World")
-=======
 def greet():
-    print("hello world")
+    print("Hello World")

 if __name__ == "__main__":
     greet()
->>>>>>> c363960 (refactor to use function)
מצב r מציג לדעתי את הפלט הכי ברור - אני רואה את שני הענפים, את השינויים של כל ענף ואת הודעות הקומיט המתאימות.

ToCode
1 419
טיפ גיט: הצגת שינויים ב Merge Commit נתון הריפו הבא:
*   a883c36 (HEAD -> main) Merge branch 'develop'
|\
| * c363960 (develop) refactor to use function
* | d55f747 fixed text
|/
* 64b74f3 initial commit
יש לנו ענף main וענף develop, בענף develop ביצענו שינוי בקומי c363960 וענף main עשה שינוי באותו זמן ובאותו קובץ בקומי d55f747. בסוף מיזגנו את השינויים מ develop בקומיט a883c36. עכשיו ננסה להסתכל אחורה בריפו ולהבין מה היו השינויים שקומיט המיזוג הכניס ואיך בדיוק הם מוזגו. ניסיון ראשון עלול להפתיע:
$ git log -p -1

commit a883c360dfae324a365d9bfb9ab881865a33f0c4 (HEAD -> main)
Merge: d55f747 c363960
Author: ynonp <ynonperek@gmail.com>
Date:   Sun Nov 30 13:56:36 2025 +0200

    Merge branch 'develop'
אני רואה את קומיט המיזוג אבל נראה שאין בו שינויים כלל! מה קורה פה? ההסבר הוא שגיט לא מציג שינויים שבוצעו בקומיט מיזוג בתור ברירת מחדל. מתוך התיעוד:
--diff-merges=<format>
    Specify diff format to be used for merge commits. Default is \off\
הסיבה היא שהוא לא יודע מול איזה קומיט להציג את ההבדלים ולכן ברירת המחדל היא לא להציג כלום. אפשרות אחת היא לבחור להציג את ההבדלים מול ההורה הראשון, במקרה שלנו main:
 $ PAGER= git log -1 -p --diff-merges=1
commit a883c360dfae324a365d9bfb9ab881865a33f0c4 (HEAD -> main)
Merge: d55f747 c363960
Author: ynonp <ynonperek@gmail.com>
Date:   Sun Nov 30 13:56:36 2025 +0200

    Merge branch 'develop'

diff --git a/demo.py b/demo.py
index 73bd701..ba6d51e 100644
--- a/demo.py
+++ b/demo.py
@@ -1,2 +1,5 @@
-if __name__ == "__main__":
+def greet():
     print("Hello World")
+
+if __name__ == "__main__":
+    greet()
מזה אנחנו מבינים שענף develop הוציא את ההדפסה של Hello World לפונקציה נפרדת בשם greet. ומה עם ענף develop עצמו? אז אין ערך 2 ל diff-merges אבל ניתן להשתמש במילה separate כדי לראות את השינויים מול כל הענפים:
$ PAGER= git log -1 -p --diff-merges=separate
commit a883c360dfae324a365d9bfb9ab881865a33f0c4 (from d55f74779962b803a89f178a08065014dd74b3ac) (HEAD -> main)
Merge: d55f747 c363960
Author: ynonp <ynonperek@gmail.com>
Date:   Sun Nov 30 13:56:36 2025 +0200

    Merge branch 'develop'

diff --git a/demo.py b/demo.py
index 73bd701..ba6d51e 100644
--- a/demo.py
+++ b/demo.py
@@ -1,2 +1,5 @@
-if __name__ == "__main__":
+def greet():
     print("Hello World")
+
+if __name__ == "__main__":
+    greet()

commit a883c360dfae324a365d9bfb9ab881865a33f0c4 (from c363960ad68f3b2875b083d7bc2b92e57df1cb7a) (HEAD -> main)
Merge: d55f747 c363960
Author: ynonp <ynonperek@gmail.com>
Date:   Sun Nov 30 13:56:36 2025 +0200

    Merge branch 'develop'

diff --git a/demo.py b/demo.py
index 3635ac7..ba6d51e 100644
--- a/demo.py
+++ b/demo.py
@@ -1,5 +1,5 @@
 def greet():
-    print("hello world")
+    print("Hello World")

 if __name__ == "__main__":
     greet()
התוצאה היא שני diff-ים בפלט אחד - ה diff הראשון מראה לי את השינויים של קומיט המיזוג מול main שהם ההעברה של ההדפסה לפונקציה. ה diff השני מראה לי את השינויים מול develop ועכשיו אני רואה שבקומיט המיזוג השתמשו באותיות גדולות בתחילת מילה בעוד שב develop ההדפסה היתה באותיות קטנות. אפשרות שלישית למתג זה היא combined או בקיצור c שמציגה את כל ההבדלים יחד:
$ PAGER= git log -1 -p --diff-merges=c
commit a883c360dfae324a365d9bfb9ab881865a33f0c4 (HEAD -> main)
Merge: d55f747 c363960
Author: ynonp <ynonperek@gmail.com>
Date:   Sun Nov 30 13:56:36 2025 +0200

    Merge branch 'develop'

diff --combined demo.py
index 73bd701,3635ac7..ba6d51e
--- a/demo.py
+++ b/demo.py
@@@ -1,2 -1,5 +1,5 @@@
- if __name__ == "__main__":
+ def greet():
 -    print("hello world")
 +    print("Hello World")
+
+ if __name__ == "__main__":
+     greet()
מצד אחד יותר קל להבין כאן מה השתנה אבל מצד שני יותר קשה להבין מאיזה ענף הגיע כל שינוי. אופציה אחרונה היא r או remerge. במצב כזה גיט מחפש את כל השינויים ומנסה לשחזר את סימני הקונפליקט שהיו כדי שנוכל לראות מה הגיע מאיפה. במקרה שלנו הפלט הוא:
$ PAGER= git log -1 -p --diff-merges=r

ToCode
1 419
היום למדתי: פרטישן לפי זמן ומפתח ראשי לקחתי טבלת פוסטגרס בשביל הסיפור נניח שזו טבלת לקוחות:
CREATE TABLE customers (
    customer_id     INT PRIMARY KEY,
    full_name       TEXT NOT NULL,
    email_address   TEXT NOT NULL,
    created_at      TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
    updated_at      TIMESTAMP WITH TIME ZONE DEFAULT NOW()
);
ואז רציתי לחלק אותה ל Partitions כדי לשפר ביצועי שליפות (רוב השליפות צריכות למצוא לקוחות חדשים). אז הלכתי ל ChatGPT וביקשתי גרסה מחולקת של הטבלה. זה הקוד שהוא הדפיס:
CREATE TABLE customers (
    customer_id     INT PRIMARY KEY,
    full_name       TEXT NOT NULL,
    email_address   TEXT NOT NULL,
    created_at      TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW(),
    updated_at      TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW()
)
PARTITION BY RANGE (created_at);
הוא כמובן טעה. פוסטגרס לא מרוצה וטוען שהמפתח הראשי צריך להיות חלק מה Partition. וזה ברור - מפתח ראשי אמור להיות ייחודי על כל השורות, אבל אם השורות מאוחסנות בטבלאות שונות אין לפוסטגרס דרך לוודא ייחודיות או לדעת באיזה Partition מפתח ראשי מסוים מאוחסן. בהנתן מפתח ראשי שלא קשור למפתח החלוקה של הטבלה, פוסטגרס עדיין יצטרך לסרוק את כל ה Partitions כדי למצוא את השורה. הפתרון של ChatGPT לא מלהיב, הוא מציע להשתמש במפתח ראשי שמורכב משני שדות (ה id וה timestamp). אנחנו לא אוהבים מפתחות ראשיים מורכבים. אופציה שניה לא מלהיבה שהוא מציע היא לוותר לגמרי על המפתח הראשי, וגם זה לא מלהיב. פתרון שנראה לי יותר טוב בתיאוריה (לא מימשתי עדיין) הוא להשתמש במפתח ראשי מסוג UUID שכולל timestamp למשל UUID7 או ULID. במפתח ראשי כזה אפשר לקחת את החלק של הזמן להיות מפתח החלוקה של הטבלה. כאן יש פוסט ארוך שמתאר את הרעיון: https://elixirforum.com/t/partitioning-postgres-tables-by-timestamp-based-uuids/60916 בקצרה זה הקוד שלו ליצירת הטבלה:
create table(:payloads, primary_key: false, options: "PARTITION BY RANGE(id)") do
  add(:id, :binary_id, null: false, primary_key: true)

  # ... other fields and references
end
והקוד שיוצר את ה Partitions יהיה משהו כזה:
CREATE TABLE #{table}_p#{start_date.year}#{month}
PARTITION OF #{table} FOR VALUES
FROM (ulid_to_uuid('#{start_ulid}'))
TO (ulid_to_uuid('#{end_ulid}'))

ToCode
1 419
נ.ב. ראינו בוובינר איך אני פותח תיקיה ריקה לגמרי ומבקש מקרסר לתכנן פרויקט מסוים (למשל משחק כרטיסיות ללימוד ספרדית או משחק סנייק), נותן לו את הטכנולוגיה והקרסר מצליח לבד לבנות את כל ה Boilerplate של הפרויקט בשפה שבחרתי, ואז ממשיך להתקין את התלויות ולתקן באיטרציה עד שהפרויקט רץ. סוכן שעובד, ועובד ועובד רעיון שלישי הוא שסוכני AI יכולים לעבוד לנצח. לפעמים הם יצטרכו הרבה איטרציות אבל בסוף הם יסתדרו ועדיף לתת להם כמה שיותר לעבוד עצמאית. בקרסר הרעיון הזה ממומש בעזרת הפיצ'רים הבאים: 1. כשסוכן מתחיל לעבוד הוא לא יעצור אחרי X איטרציות לשאול אם אתם בטוחים שאתם רוצים להמשיך. הוא פשוט ימשיך עד שתעצרו אותו. 2. אין בעיה להפעיל כמה סוכנים יחד והם מסתדרים לא לערוך את אותם חלקים בקבצים. אפשר לעבור בין הסוכנים הפעילים באותה רשימה אנכית של סוכנים. 3. סוכן יכול להפעיל פקודות Shell ללא הגבלה בתוך מה שהם קוראים Sandbox. אותו סנדבוקס זה בעצם כלוב שלא מאפשר לקרסר לגשת לקבצים מחוץ לפרויקט או לפתוח חיבורי רשת. סדנבוקס כרגע נתמך רק על מק והם אמרו שהם עובדים על תמיכה ללינוקס. גם כאן המטרה היא לאפשר לסוכן יותר עצמאות ולהוריד את הסיכון. בעבודה עם קרסר השתמשתי בזה כדי להריץ בדיקות ולתקן את הקוד או קוד הבדיקות עד שהבדיקות יעברו. זה לוקח לסוכן זמן אבל בסוף הוא נותן גרסה עובדת של הקוד ואותה אני יכול לקרוא כדי לוודא שהוא לא עשה שטויות. סוכן מכל מקום רעיון רביעי שקרסר מקדמים הוא היכולת להשתמש בסוכן מכל מקום. זה אומר כמובן תמיכה ב MCP אבל יותר מעניין זה החיבור המובנה שלהם עם דפדפן ועם מסוף. מתוך ה Terminal המובנה בקרסר אני יכול בכל שלב ללחוץ Cmd+K ולקבל Popup של סוכן AI שיש לו גישה מלאה לקריאה וכתיבה למסוף. כך אם אתם נתקעים ואיזה פקודת Shell או git לא עובדת תמיד תוכלו לבקש עזרה בדיוק מאיפה שאתם. בצד הדפדפן הסוכן יכול להפעיל דפדפן, לנווט לאתרים, ללחוץ, להקליד, לגלול, לקחת צילומי מסך ולהסתכל בקונסול או בבקשות הרשת של הדפדפן. בצורה כזאת הסוכן יכול באופן עצמאי לנסות את שינויי הקוד שלו עד שדברים עובדים או לעזור לכם לדבג. סך הכל קרסר הוא סביבת עבודה מעניינת שמציעה גישה שונה מ VS Code לעבודה עם סוכנים בדגש על הרצת הסוכנים במקביל, מכל מקום ובאופן יותר עצמאי.

ToCode
1 419
התקנתי קרסר, מה עכשיו? השבוע בקבוצת מדברים AI הראיתי צעדים ראשונים עם Cursor. בדרך כלל אני מוציא סיכומי וידאו אחרי המפגשים האלה אבל הפעם תפסתי וירוס כך שגם בוובינר הייתי צרוד ובטח לא הצלחתי להקליט סיכום אחריו. במקום זה אני כותב כאן את עיקרי הדברים ומקווה שזה יעזור לאלה מכם שלא הגיעו. מה זה Cursor קרסר היה סביבת הפיתוח משולבת AI הראשונה שראיתי. היא יצאה עוד ב 2023 ממש עם ChatGPT ולדעתי היתה שם לפני VS Code וקופיילוט ובטח לפני הסביבות המתחרות Windsurf ו Trae. עוד מההתחלה החברים בקרסר התלבטו אם לייצר תוסף ל VS Code או לקחת את הקוד עצמו (כי VS Code הוא בקוד פתוח) ולייצר על בסיסו עורך משלהם ובשלב מוקדם מאוד הם הבינו שכדי לתת את מלוא הערך של ה AI הם רוצים לייצר עורך שלהם. היום כשהם כבר עברו את גרסה 2 עורך הקוד שלהם מתפקד באופן עצמאי מצוין ומציג אג'נדה ברורה לגבי חלוקת העבודה בין מפתחים ל AI. נקודת הפתיחה - חקירת הפרויקט הרעיון המרכזי הראשון שאנחנו רואים כבר כשמתקינים קרסר הוא שני כפתורי בחירה בין "מצב עורך" ל"מצב סוכן". מצב העורך זה מה שנראה כמו VS Code שאנחנו מכירים אבל את הוובינר התחלנו ממסך הסוכן. נשים לב לכמה דברים מעניינים במצב זה: 1. הסוכנים מופיעים בתור רשימה אנכית כלומר אחד מתחת לשני. רשימה כזאת מאפשרת ניווט מהיר בין מספר סוכנים שעובדים במקביל. 2. השיחה עם הסוכן מופיעה במרכז המסך ומקבלת לפחות חצי מסך לפעמים יותר. 3. עורך הטקסט נמצא בצד השמאלי של המסך ונראה שאיבד קצת מחשיבותו. אני מתחיל בהפעלת קרסר על פרויקט קיים ושולח אותו לחקור. נשים לב שמתחת לתיבת ההודעה יש כפתור לבחירת מצב העבודה של הסוכן ובשביל לחקור אני בוחר במצב Ask. הנה כמה פרומפטים לדוגמה למצב זה: 1. Describe the project architecture. Use mermaid diagrams. 2. Find the top 10 technical debts the project. 3. Describe the test framework used, what parts of the project are covered with tests and what important flows are not tested. 4. Find top 10 potential performance bottlenecks the project may have. 5. Describe a full flow (include specific files and line numbers) from web request to response for a URL. כל חיפוש כזה יציג לי הסבר טקסטואלי וגם רשימה של קבצי קוד לחקור. כשאני נכנס לקובץ קוד אני יכול לסמן פונקציה, לבחור ב Popup באפשרות Add to chat ואז להמשיך עם פרומפטים ספציפיים לפונקציה זו כמו: 1. Describe the API of the function - what are its input parameters, expected data in the database, output format and when is it used. 2. Is the function covered with existing tests? point me to the relevant tests. 3. Code review the function and suggest an alternative implementation. כשדברים מתחילים להסתבך אני יכול לעבור לטאב העורך כדי לצלול לתוך הקוד ואז לחזור לטאב הסוכנים כדי לראות מה עושים הסוכנים האחרים. זה הכח הראשון של קרסר - ניהול עבודה במקביל של כמה סוכנים כולם חוקרים במקביל את הפרויקט ומתכננים עבורי את העבודה. תיקוני קוד - מתכנון לביצוע רעיון שני שקרסר מקדמים הוא פיתוח לפי דרישה או Spec Driven Development. נחזור לתיבת הסוכן ונניח שאני מסתכל על הסוכן שחקר את סביבת הבדיקות של הפרויקט ותיאר לי איזה תהליכים מכוסים על ידי בדיקות ואיזה לא. עכשיו אני מעביר את אותו סוכן למצב Plan, בוחר flow אחד ומבקש: > Create a test plan to test flow X. Be specific about the tests to execute, the data to create in the DB and the expected results. התוצאה היא קובץ Markdown שמכיל רשימה של בדיקות עם הוראות איך לכתוב את הבדיקות האלה. בחלונות אחרים נוכל לבקש לתכנן מיגרציה לבסיס הנתונים, שכתוב של פונקציה או של flow בקוד, עדכון UI או כל פיצ'ר או באג שאנחנו צריכים. אחרי שהתוכנית מופיעה אני יכול להמשיך לדבר עם הסוכן כדי לעדכן אותה, למשל אני יכול לבקש ממנו להוסיף בדיקות עומסים או לוודא שהבדיקות יכולות לרוץ במקביל. אני יכול גם לערוך בעצמי את קובץ ה Markdown של התוכנית כשצריך שליטה יותר מדויקת בפרומפט. אחרי שסיימתי לעבוד על התוכנית באותו חלון של התוכנית אני מזהה כפתור Build. לחיצה עליו מעבירה את הסוכן למצב ביצוע ושם הוא מתחיל לכתוב את הקוד.