fa
Feedback
ToCode

ToCode

رفتن به کانال در Telegram

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

نمایش بیشتر
1 419
مشترکین
-124 ساعت
اطلاعاتی وجود ندارد7 روز
-430 روز
آرشیو پست ها
ToCode
1 419
פתרון Advent Of Code 2025 יום 8 הי חברים פוסט אחרון ברצף הזה של Advent Of Code כמו השניים הקודמים גם כאן יש לנו תרגיל שנראה פשוט ואז מגיע הטוויסט בחלק השני ואיתו הזדמנות לדבר על Trade Offs ופתרונות לא מושלמים. בואו נצא לדרך. האתגר יום 8 מציג לנו בתור קלט רשימה של מיקומים בעולם תלת מימדי:
162,817,812
57,618,57
906,360,560
592,479,940
352,342,300
466,668,158
542,29,236
431,825,988
739,650,466
52,470,668
216,146,977
819,987,18
117,168,530
805,96,715
346,949,466
970,615,88
941,993,340
862,61,35
984,92,344
425,690,689
אפשר לדמיין שאלה מיקומים של איזשהם רכיבים שמתחברים למעגלים חשמליים אבל זה לא מאוד חשוב. עכשיו מתחילים לולאה שמוצאת את שתי הנקודות הקרובות ביותר ומחברת אותן. איטרציה שניה מוצאת את שתי הנקודות הבאות הכי קרובות ומחברת אותן וכך ממשיכים n איטרציות. מגדירים קבוצה של נקודות מחוברות בתור "מעגל" והשאלה היא מה מכפלת הגדלים של שלושת המעגלים הגדולים ביותר. הפתרון נשים בצד את סיפור הרקע ונשים לב לדילמה - מה עולה ומתי משלמים. שתי אפשרויות: 1. אנחנו למצוא שתי נקודות קרובות ואז "לרשום" בצד שאנחנו מחברים אותן וכך לבנות מבנה נתונים של גרף. אחרי אלף חיבורים נסרוק את הגרף ונחפש מעגלים. 2. אנחנו יכולים אחרי כל חיבור להסתכל מה חיברנו ולאחד את הצמתים לגרף. במצב כזה הכתיבה היא יקרה כי כל פעם שאני יוצר חיבור בין p ל q אני צריך לסמן גם ש p מחובר ל q אבל גם שכל הנקודות שמחוברות ל p עכשיו מחוברות לכל הנקודות שמחוברות ל q כלומר לאחד את המעגלים. מאחר ואנחנו יודעים מה מספר האיטרציות יהיה יותר קל לפתור את התרגיל בשיטה הראשונה. לצערי (או לשמחתי) זה לא מה שעשיתי והפתרון שכתבתי השתמש בשיטה השניה כלומר כל נקודה יוצגה על ידי קבוצה וכל חיבור איחד שתי קבוצות. זה אומר שכל איטרציה עולה קצת יותר אבל זיהוי המעגלים בסוף הוא בחינם. זה הקוד ברובי, הכי ארוך עד כה:
def line_to_pos(l)
  l.chomp.split(',').map(&:to_i)
end

def distance(p1, p2)
  (p1[0] - p2[0]) ** 2 + (p1[1] - p2[1]) ** 2 + (p1[2] - p2[2]) ** 2
end

class Day8
  def initialize(filename)
    lines = File.read(filename).lines
    @circuits = lines.map do |line|
      pos = line_to_pos(line)
      [pos, Set.new([pos])]
    end.to_h

    distances = Hash.new {|h, k| h[k] = [] }
    lines.each do |from|
      lines.each do |to|
        next if from == to
        p = line_to_pos(from)
        q = line_to_pos(to)
        d = distance(p, q)
        distances[d] << [p, q] unless distances[d].include?([q, p])
      end
    end
    @distances = distances.keys.sort.flat_map {|k| distances[k] }
  end

  def connect(p, q)
    c_p = @circuits[p]
    c_q = @circuits[q]
    return 0 if c_p == c_q

    @circuits[p].merge(c_q)
    @circuits[q].each do |qq|
      @circuits[qq] = @circuits[p]
    end
    return 1
  end

  def part1
    cables = 1_000
    i = 0
    while cables > 0
      next_p, next_q = @distances[i]
      cables_used = connect(next_p, next_q)
      cables -= 1
      i += 1
    end
    @circuits.values.uniq.map {|s| s.size }.sort.reverse[0..2].reduce(1) {|acc, val| acc * val }
  end
end
למרות שהפתרון נכון ועובד קלוד זיהה את ה"באג הקריטי" הבא:
Problem: Only elements in c_q get updated. Elements in c_p that aren't p may still point to the old set.
הזיהוי הזה בדיוק מראה לנו למה עדיין קשה לקבל Code Review מ AI. כשקוראים את הקוד ואת ההערה זה נראה מאוד נכון, באמת אין סימטריה בפונקציה connect בין p ו q. לקבוצה של p אני רק עושה merge אבל בקבוצה של q אני ממש רץ בלולאה ומעדכן את כל ההפניות. למה לא הייתי צריך לעשות את זה גם ב p ? התשובה ברורה למי שמבין את זרימת המידע במערכת - הנקודות האחרות ב p כבר מצביעות על אותו Set כמו p כי עדכנתי את ההצבעה שלהן בחיבורים קודמים. אין טעם לרוץ ולעדכן מחדש. הבאג השני שקלוד מזהה בטעות ב Code Review הוא:
Cables are decremented even when connect returns 0 (already connected):

ToCode
1 419
......|........
......^|^......
.......|.......
.....^|^.^.....
......|........
....^.^|..^....
.......|.......
...^.^.|.^.^...
.......|.......
..^...^|....^..
.......|.......
.^.^.^|^.^...^.
......|........
והשאלה היא כמה עולמות אפשריים כאלה קיימים. אז ברור שאנחנו לא יכולים "לנסות" את כל העולמות כלומר לחשב ולשמור כל מסלול אפשרי. זה פשוט ייקח הרבה יותר מדי פעולות בגלל החישובים הכפולים. וברגע שחשבתם את המילה "חישובים כפולים" מיד אתם יודעים שנכנסתם לארץ התכנות הדינמי. תכנות דינמי בסך הכל אומר שאנחנו שומרים תוצאות של חישובי ביניים כדי לא לחזור עליהם. בסיפור של הקרן אני רוצה לסמן Slitter מסוים ולהסתכל עליו שימו לב לכוכבית בציור הבא:
.......S.......
.......|.......
......|^.......
......|........
......^|^......
.......|.......
.....^|^.^.....
......|........
....^.*|..^....
.......|.......
...^.^.|.^.^...
.......|.......
..^...^|....^..
.......|.......
.^.^.^|^.^...^.
......|........
הדרך הלא נכונה לחשוב על התרגיל הזה, וזו שתייצר חישובים כפולים, היא להגיד שאנחנו מגיעים לכוכבית ואז מפעילים קריאה רקורסיבית וסופרים כמה עולמות יש בצד שמאל שלה וכמה עולמות יש בצד ימין שלה. בשביל לראות למה זה לא עובד שימו לב לשני הספליטרים שמעל הכוכבית (מסומנים בכרוכית):
.......S.......
.......|.......
......|^.......
......|........
......^|^......
.......|.......
.....@|@.^.....
......|........
....^.*|..^....
.......|.......
...^.^.|.^.^...
.......|.......
..^...^|....^..
.......|.......
.^.^.^|^.^...^.
......|........
כל אחד מהם יכול לשלוח קרן שתגיע לכוכבית. החישוב הכפול קורה כשאנחנו סופרים את מספר העולמות מהכוכבית למטה פעמיים: פעם אחת כשהגענו לכוכבית מהכרוכית השמאלית ופעם שניה וזהה כשאנחנו מגיעים לכוכבית מהכרוכית הימנית. ספירת פתרונות לא עובדת בגלל שהיא מייצרת המון חישובים כפולים. מה עושים במקום? תכנות דינמי אומר שכדאי לנו לשמור בכל משבצת כמה אפשרויות יש להגיע אליה. בכל שורה נוכל להסתכל על השורה הקודמת כדי לעדכן את המונה: אם מעלינו היה Splitter הערך החדש הוא 0, אחרת סוכמים את ערך המשבצת שמעלינו ואת האלכסונים שלמעלה אם הם החזיקו Splitter. בסוף נוכל לסכום את כל האפשרויות של כל המשבצות בשורה האחרונה וכך נקבל את מספר העולמות. הנה זה ברובי לא מסובך בכלל:
  def part2
    beams_through = []

    File.read(@filename).lines.each do |line|
      if beam_index = line =~ /S/
        puts "--- START ---"
        beams_through = line.chars.map {|c| c == "S" ? 1 : 0 }
      end
      
      splitter_indexes = Set.new(line.find_all_indexes('^'))
      beams_through = beams_through.each_with_index.map do |previous_count, index|
        if splitter_indexes.include?(index)
          0
        else
          value = beams_through[index]
          value += beams_through[index - 1] if splitter_indexes.include?(index - 1)
          value += beams_through[index + 1] if splitter_indexes.include?(index + 1)

          value
        end
      end

      pp beams_through
    end
    beams_through.sum
  end

ToCode
1 419
פתרון Advent Of Code 2025 יום 7 אני רוצה להמשיך בסדרת הפתרונות של Advent Of Code ולדבר על יום 7, יום שלימד אותי שתכנות דינמי זה דווקא די כיף ולא מסובך. כמו אתמול גם פה החלק הראשון פשוט והחלק השני דורש את המאמץ. האתגר נתון קלט שמתאר סוג של מסלול מלמעלה למטה:
.......S.......
...............
.......^.......
...............
......^.^......
...............
.....^.^.^.....
...............
....^.^...^....
...............
...^.^...^.^...
...............
..^...^.....^..
...............
.^.^.^.^.^...^.
...............
במסלול יש לנו קרן שמתחילה מהאות S בשורה הראשונה ויורדת כלפי מטה. כל פעם שהיא פוגעת ב ^ היא מתפצלת ל-2 אחת בכל צד של ה ^ לדוגמה אחרי שתי שורות נקבל:
.......S.......
.......|.......
......|^|......
...............
......^.^......
...............
.....^.^.^.....
...............
....^.^...^....
...............
...^.^...^.^...
...............
..^...^.....^..
...............
.^.^.^.^.^...^.
...............
ובסוף נגיע ל:
.......S.......
.......|.......
......|^|......
......|.|......
.....|^|^|.....
.....|.|.|.....
....|^|^|^|....
....|.|.|.|....
...|^|^|||^|...
...|.|.|||.|...
..|^|^|||^|^|..
..|.|.|||.|.|..
.|^|||^||.||^|.
.|.|||.||.||.|.
|^|^|^|^|^|||^|
|.|.|.|.|.|||.|
המשימה היא לחשב כמה פעמים הקרן מתפצלת. הפתרון אני מתחיל בהגדרת פונקציית עזר שמוצאת בשורה את כל האינדקסים של הספליטרים:
class String
  def find_all_indexes(ch)
    chars.each_with_index.filter {|c, i| c == ch }.map {|c, i| i }
  end
end
בגלל שזה רובי אני יכול להגדיר את הפונקציה בתור הרחבה ל Stirng ועכשיו אפשר לכתוב:
"abcabcabc".find_all_indexes("a")
כדי להפעיל את הפונקציה על המחרוזת שמשמאל לנקודה. הקוד עצמו בתוספת הערות של AI הוא:
  def part1
    beam_indexes = Set.new
    count = 0

    File.read(@filename).lines.each do |line|
      # Initialize beam positions when we encounter the start marker
      if beam_index = line =~ /S/
        puts "--- START ---"
        beam_indexes = Set.new([beam_index])
      end

      # Find all splitter positions in current line
      splitter_indexes = Set.new(line.find_all_indexes('^'))
      # When a beam hits a splitter, it splits into two beams (left and right)
      splitted_beams = Set.new((splitter_indexes & beam_indexes).flat_map {|i| [i-1, i+1] })
      # Count interactions with splitters
      count += (splitter_indexes & beam_indexes).size
      # Beams that don't hit splitters continue straight
      continuing_beams = beam_indexes - splitter_indexes
      # Update beam positions: continuing beams + newly split beams
      beam_indexes = continuing_beams + splitted_beams
    end

    count
  end
האלגוריתם כמו שאפשר לראות מהקוד הוא בסך הכל פעולות על קבוצות: לוקחים את כל האינדקסים של ה ^ לקבוצה אחת ואת כל האינדקסים של הקרניים בקבוצה שניה והחיתוך בין שתי הקבוצות הוא בדיוק מספר הפיצולים של אותה שורה. החישוב הוא בשורה:
count += (splitter_indexes & beam_indexes).size
כששלחתי את הקוד לקלוד עבור Code Review הוא טען שפעולת חיבור של שתי קבוצות ברובי מחזירה מערך אבל בדיקה בקונסול מראה לי שזו טעות וחיבור שתי קבוצות מחזיר קבוצה:
3.3.5 :005 > (Set.new + Set.new).class
 => Set
הוא גם טען ששכחתי לעשות require ל set וזו גם טעות שלו. שני הדברים מלמדים אותי שקלוד כנראה חושב שאני בגרסה ישנה יותר של רובי. סך הכל הקוד עבד בסדר גמור והחזיר תוצאה נכונה. הטוויסט - תכנות דינמי החלק השני של השאלה הכניס את הטוויסט. דמיינו שבמקום שהקרן תתפצל הזמן עצמו מתפצל ויש לנו עולם אחד בו הקרן הלכה ימינה ועולם אחר בו היא הלכה שמאלה. כל עולם כזה ימשיך בתורו להתפצל לעוד עולמות כשהקרן תמשיך לפגוע בעוד Splitters. זו דוגמה לעולם אפשרי אחד:
.......S.......
.......|.......
......|^.......
......|........
.....|^.^......
.....|.........
....|^.^.^.....
....|..........
...|^.^...^....
...|...........
..|^.^...^.^...
..|............
.|^...^.....^..
.|.............
|^.^.^.^.^...^.
|..............
וזו דוגמה לעוד עולם אפשרי:
.......S.......
.......|.......
......|^.......

ToCode
1 419
פתרון Advent Of Code 2025 יום 6 אני ממשיך עם סדרת Advent Of Code לשמחתנו השנה יש רק 12 חידות אז יש סיכוי שאצליח לפתור ולפרסם את כולם לפני שיעלו האתגרים של 2026. אנחנו היום באמצע הדרך עם יום 6 ותרגיל שאני חשבתי שהיה סופר מעניין. בואו נראה את המספרים. האתגר נתון קלט שנראה די פשוט:
123 328  51 64 
 45 64  387 23 
  6 98  215 314
*   +   *   +
יש פה טורים של מספרים ובתחתית כל טור מופיע סימן פלוס או כפול. המשימה שלנו לכפול או לחבר את המספרים בטור (לפי הסימן) ובסוף לחבר את התוצאות. בדוגמה התוצאה היא 4277556. הפתרון הדרך לפתרון די פשוטה: קוראים את הקלט שורה שורה, שוברים כל שורה לפי רווחים, כי אנחנו יודעים שיש את אותו מספר עמודות בכל שורה ובונים מערך דו מימדי של כל הגריד. בסוף לוקחים את הסימן האחרון בכל עמודה בתור פעולה ומפעילים אותה על כל הפריטים באותה עמודה. זה הקוד ברובי:
class Day6
  def initialize(filename)
    @filename = filename
  end

  def part1
    columns = []
    File.read(@filename).lines do |line|
      columns << line.strip.split(/\s+/)
    end
    @operators = columns.pop
    @values = columns
    results = @operators.each_with_index.map do |op, idx|
      @values.map {|v| v[idx].to_i }.reduce(op.to_sym)
    end
    pp results.sum
  end
end
הטוויסט אחרי שמגישים את החלק הראשון מגלים את הטוויסט בתרגיל - שימו לב אומרים לנו שחלק מהמספרים מיושרים לימין וחלק לשמאל:
123 328  51 64 
 45 64  387 23 
  6 98  215 314
*   +   *   +
בעצם המספרים בכל טור כתובים מלמעלה למטה ולא רגיל משמאל לימין, לכן בטור הימני המספר הראשון הוא 4 (הטור הימני ביותר), אחריו יש לנו טור בו יש את 4, 3 ו-1 ולכן המספר הוא 431 והמספר האחרון בטור הימני הוא 623. חיבור שלושתם נותן 1058. השינוי הזה משפיע ישירות על הבלוק:
File.read(@filename).lines do |line|
  columns << line.strip.split(/\s+/)
end
בחלק הראשון חשבנו שאפשר להתעלם מרווחים ושיש אותו מספר של עמודות בכל שורה. עכשיו אנחנו מגלים שעדיין יש אותו מספר עמודות בכל שורה אבל חשוב לשמור על הריווח כי 64 שמיושר לשמאל לא מתנהג כמו 51 שמיושר לימין שיושב ממש משמאלו. האתגר השני שלא רואים בנתוני הדוגמה הוא שכל עמודה יכולה להיות באורך שונה. בנתוני הדוגמה כל העמודות כללו מספרים בני 3 ספרות ולכן לכולן היה אותו אורך אבל במקרה הכללי קל לראות שאם יש טור עם מספרים בני שתי ספרות או ספרה אחת נדרש טיפול שונה, כלומר קלט כזה לגמרי יכול לקרות:
123 32  51 64 
 45 64 387 23 
  6 98 215 314
*   +  *   +
מה עושים? הפעם אנחנו צריכים לזהות מה אורך כל עמודה ואז לפצל את השורה לעמודות בצורה קשיחה לפי מספר התווים שאנחנו יודעים שיש בכל עמודה. אפשר להגיד שבחלק הקודם שברנו את השורה לפי תו הפרדה ועכשיו אנחנו שוברים לפי אורך או אינדקסים. בשביל לגלות את האורך של כל עמודה אפשר להסתכל על השורה האחרונה שורת הפעולות. זה הקוד ברובי לחלק השני:
  def part2
    lines = File.read(@filename).lines
    operators_line = lines.pop
    column_sizes = operators_line.scan(/[+*]\s*/).map {|i| i.size - 1}
    data = lines.map(&:chomp).map do |line|
      column_sizes.map {|s| r = line.slice!(0, s); line.slice!(0, 1); r }
    end
    operators = operators_line.strip.split(/\s+/).map(&:to_sym)
    columns = (0...operators.size).map {|col| data.map {|d| d[col] } }

    values = columns.map do |column|
      (0...column[0].size).map {|i| column.map {|c| c[i] }.join('').to_i }
    end

    @results = operators.each_with_index.map {|o, i| values[i].reduce(o) }

    pp @results.sum
  end
סך הכל אני מודה שבחלק השני הסתבכתי קצת עם האינדקסים וכנראה אפשר לראות את זה כשקוראים את הקוד. למרות זאת אחרי כמה בעיטות הקוד עבד ומצליח לטפל בכל מקרי הקצה שמצאתי ולהגיע לתשובה הנכונה.

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

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

ToCode
1 419
לייטנסי שליחת פקודת insert של אלף שורות משמעותית יותר מהירה משליחת אלף פקודות insert. כשנסתכל על לוחות הבקרה של שרתים שמבצעים יותר מדי קריאות ל DB לא נראה שמישהו עובד קשה מדי: הרשת לא עמוסה, המעבד לא מזיע והזכרון ריק. כולם מחכים שמעט מידע יעבור מצד לצד. אפשר לדמיין את זה כמו צינור רחב מאוד שמישהו מזרים דרכו זרזיף מים. הצינור פנוי לגמרי, אם היו לך עוד מים להעביר הם לא היו צריכים לחכות, אבל אתה החלטת להעביר את המים שלך בטיפטופים. השבוע נעזרתי ב AI לכתוב סקריפט שמעביר מידע ממקום למקום ב Batch-ים. הקריאה אכן בוצעה ב Batch עם limit כמו שתכננתי, אבל בכתיבה קרה משהו אחר. כנראה בגלל שהיתה בקוד פונקציה שכותבת שורה בודדת ל DB ה AI השתמש בה בלולאה כדי לכתוב את כל ה Batch במקום לכתוב פונקציה חדשה שתכתוב את כל ה Batch ב insert אחד. את הבדיקה הרצתי על בסיס נתונים מקומי ולכן לא ייחסתי לזה חשיבות. בהרצת הסקריפט בסביבה אמיתית האיטיות היתה מאוד ברורה. עם הפרומפט הנכון לקח ל AI רק כמה דקות לכתוב את הפונקציה של ה Bulk Insert ולעדכן את הסקריפט שישתמש בה במקום בלולאה הישנה. זה עבד בנסיון הראשון. עכשיו לשאלה - האם זה באג? בעולם שלפני ה AI המעבר בין שתי הגרסאות היה לוקח לפחות חצי יום. יש פה פונקציה של 100 שורות לכתוב ועוד שינויי קוד במקומות אחרים מפוזרים. ברור מה צריך לעשות אבל ה"איך" היה דורש כתיבה ובדיקה. היום החצי יום הזה מבוצע בחמש דקות ומספיק מבט זריז כדי להבין שזה הצליח ולהריץ על השרת. בעולם שלפני ה AI "גילוי" של בעיית latency רק בהרצה הראשונה על נתוני אמת היה מתסכל את כולם. בעולם שאחרי ה AI זה כאילו יש מתג, המעבר משיטת עבודה אחת לאחרת הוא בסך הכל לחיצה על כפתור. קסם. בעולם שלפני ה AI היה חשוב לתכנן מראש ל Latency של סביבת הפרודקשן. בעולם שאחרי ה AI חשוב לבנות את הקוד בצורה שתאפשר לשנות את ההחלטה בלחיצת כפתור. זה השינוי הגדול שאנחנו צריכים לאמץ וההשקעה הגדולה בתשתית שנצטרך לבצע, לבנות פרויקטים ש AI יוכל לשפר.

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

ToCode
1 419
ללמוד לחשוב על בעיות קטנות וגדולות האם ללמוד לחשוב על שאלות קטנות עוזר לנו ללמוד לחשוב על שאלות גדולות? האם לדעת לכתוב סקריפט שממיין מספרים נותן לך בהמשך כלים לבנות ארכיטקטורה גדולה יותר של מערכות מורכבות? השאלה הזאת עומדת בלב "החלק הטכני" בראיונות העבודה לתכנות, החלק שתקופה ארוכה הטיל אימה על מחפשי עבודה רבים. אין ספק ששאלות האלגוריתמים הקטנות שמופיעות בראיונות עבודה נגמרות שם וברוב מוחלט של עבודות הפיתוח אנחנו לא כותבים פונקציית מיון מאפס. וכן צריך לזכור את ה AI כי הוא כבר חלק מהעולם ולשאול "האם פתרון בעיות תכנות ש AI יכול לפתור שקול לפתרון תרגיל חשבון עם מחשבון?", כלומר נחמד אבל חסר ערך מעשי. גם אני עדיין מחפש את התשובה אני רוצה לשתף כמה מחשבות בכיוון: 1. הרבה בעיות של תוכניות קטנות לא קיימות בתוכניות גדולות ולהיפך. 2. יש דברים משותפים בין בעיות קטנות וגדולות. שני המקרים דורשים צורת חשיבה מסוימת, שני המקרים דורשים בנייה של דבר גדול מחלקים קטנים יותר ומחשבה על אינטרקציה בין חלקים שונים, שני המקרים עשויים להפתיע כשהנחות היסוד משתנות בגלל שינוי אפיון. 3. מה קורה בתחומים אחרים? ללמוד לנהוג על גיר ידני יעשה אותך נהג טוב יותר עם הגיר האוטומטי? ללמוד לקרוא מפה ייתן לך יותר כלי ניווט בהשוואה לניווט עם GPS? לפתור תרגילים בלי מחשבון יעזור לך בהוכחות מתמטיות? לכתוב מכתבים לחברים יעזור לך לכתוב ספר שירה טוב יותר? לפעמים, אבל נשמע שבהרבה מקרים אנחנו בוחרים ללמוד את "השיטה הישנה" רק כגיבוי, למקרה שהטכנולוגיה לא תהיה שם. אנחנו לא חושבים שמי שיודע לנווט עם מפה ישתמש בווייז טוב יותר. 4. אנחנו כן חושבים שהבנה של היסודות עוזרת לפתור בעיות מורכבות. הדבר החשוב הוא להבין איך דברים עובדים. להבין למה נוסחה מתמטית מסוימת נכונה חשוב הרבה יותר מאשר להצליח לפתור מהר יותר תרגיל באמצעותה. 5. האם אפשר באמת להבין איך דברים עובדים בלי קודם לכתוב אותם בעצמך? האם אפשר להבין מתמטיקה בלי לפתור לפני זה תרגילים בחשבון? האם היה אפשר לתת לילדים מחשבון מכתה א? אני מרגיש שלא אבל לא לגמרי מבין למה. לכאורה עברנו את ההתלבטות הזאת כשהופיע Stack Overflow. עד אז היינו צריכים לקרוא תיעוד ואחרי SO הספיק לנו חיפוש מהיר בגוגל כדי להגיע לכל תשובה. אני חושב שהקפיצה היום עם ה AI היא גדולה יותר וזה הופך את השאלה לבוערת יותר. אין יותר בעיות קטנות ש AI לא יכול לפתור. זה לא קיים. הוא עובר ראיונות עבודה, הוא פותר את פרויקט אוילר, הוא פותר מבחנים. אין לי דרך היום בקורס לתת בעיה קטנה לתרגול ש AI לא יפתור יותר טוב מהתלמידים. ועדיין אני מרגיש שיהיה קשה למישהו ללמוד ריאקט בלי לכתוב תוכניות קטנות לפני שמתחילים לכתוב תוכניות גדולות. אבל אולי זה רק בגלל שגדלתי בעולם הישן.

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

ToCode
1 419
בדיקות של AI נגד בדיקות של בני אדם איזה כיף לעשות 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 בדיקות באותו סגנון.