ToCode
Відкрити в Telegram
1 419
Підписники
-124 години
Немає даних7 днів
-430 день
Архів дописів
1 419
עוד מאותו דבר
קלוד קוד יודע לפתור את כל Advent Of Code של השנה בשעתיים. זה בטוח יותר מהר ממני וכנראה יותר מהר מרוב בני האדם שניסו. וככל שהכלים האלה משתפרים אנחנו מבינים טוב יותר את העבודה שלנו כמהנדסי תוכנה:
1. פתרון בעיות מורכבות, כאלה שלא מופיעות בספר הלימוד, ודורשות אינטגרציה של חומר והתאמה אישית של רעיונות כלליים לעולם תוכן ספציפי.
2. תושיה - למצוא דרכים בטוחות וטובות לעשות דברים גם כשהרעיון הראשון לא עובד.
3. עבודת צוות.
4. תקשורת טובה עם עמיתים ומנהלים.
5. אמינות.
6. מבנה פרויקט שמאפשר לאוטומציה להתקדם יותר מהר עם הקוד - זה כולל גם את הקוד עצמו וגם הקמת סביבת בדיקות, מסמכים מלווים, יכולת להרים פרויקט בלחיצה אחת בסביבה נייטרלית, נתוני seed.
7. הבנת המסגרת, המגבלות, היכולות, הארגון.
בראייה קדימה הפיצ'רים היחידים שמפתחים צריכים לבנות הם פיתוחי תשתית. כל מה שהוא "עוד מאותו דבר" ניתן ל AI, עדיף לאנשי פרודקאקט טכניים ומוכשרים שישמחו לשחק עם הכלים החדשים. שתי השאלות החשובות שאנחנו חייבים לשאול היום על כל פיצ'ר שאנחנו בונים:
1. האם AI יכול לבנות את זה?
2. אם לא, איך צריכה להיראות המערכת כדי ש AI יוכל לבנות את זה?
1 419
אז ה AI טעה בשם של הפונקציה
איך מבלבלים את קלוד? מסתבר שבקלות ואפילו בלי להתכוון. זה הקוד שהיה צריך לכתוב:
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 זה קודם כל לשים לב שהטעות שלו היא הגיונית לגמרי ואז לתקן את הפרויקט.1 419
1. סיכוי טוב שבעשור הקרוב נמשיך לראות את שני סוגי התפקידים. יהיו לא מעט אנשים שייכנסו לקוד ואם אתם אוהבים את ההיבט הטכני של איך דברים בנויים ועובדים זה מסלול שכדאי לקחת. יש פה גם הרבה כיף. יהיו גם לא מעט אנשים שיאפיינו מערכות ופיצ'רים לסוכני AI ואם אתם מסוג האנשים שאוהבים לתכנן מה לבנות החיים שלכם כנראה יהיו הרבה יותר מעניינים בזכות ה AI. היום אנחנו קוראים לתפקידים מהסוג הראשון מפתחי תוכנה ומהסוג השני מנהלי מוצר, אבל ההיבטים הספציפיים של כל תפקיד הולכים בוודאי להשתנות.
2. אם העולם של פיתוח תוכנה מעניין אתכם כדאי להסתכל על AI בתור "מורה" ולא בתור "עובד". אתם לא בתחרות איתו, אתם כן צריכים להבין כל שורת קוד ולהיות מסוגלים לכתוב כל מערכת בעצמכם.
3. מפתחי תוכנה עדיין לומדים הכי טוב דרך הקלדה. AI לא משנה את זה. כדאי לחשוב על AI כמו ספר לימוד ולא כמו מחשבון.
ולחבר מהפתיח הייתי אומר - אתה צודק. כל מה שאתה כותב היום ה AI כותב טוב יותר. כרגע מבחוץ זה נראה כאילו AI כותב קוד יותר טוב ויותר מהר מכל האנשים שאתה מכיר וקשה להבין למה צריך מתכנתים בעולם. אבל דברים שרואים מכאן לא רואים משם והאמת היא שהבנה מערכתית, היכרות לעומק עם עולם פיתוח התוכנה ובעיקר יכולת ניתוח וחשיבה עם המושגים בעולם זה היא עדיין רחוקה מהיכולות של מערכות AI. במובן מסוים AI העלה את הרף והגביר את קצב הלימוד האפשרי. בזכות ה AI אתה לומד יותר מהר, אבל כך גם כל המתחרים שלך שמתמודדים על אותם תפקידים בתעשייה.
ובאותו נושא שווה לקרוא גם את הפוסט האחרון של הסולידית:
https://www.hasolidit.com/וייב-קודינג-לעצמאות-כלכלית. שאלת המפתח שלה:
"אם הדיוטה כמוני יכולה “לשאול” מיומנות בהינף רגע, יכולת שאחרים השקיעו שנים כדי לרכוש ולפתח, האם עוד אפשר בכלל להתייחס אליה כמיומנות?"
אני מבין למה זה נראה ככה מבחוץ. מבפנים אנחנו יודעים שרוב מוחלט של המפתחים בתעשייה לא בונים מערכות שאפשר להחליף עם Vibe Coding. כשהתחיל טרנד ה Outsource חבר הזהיר אותי שעוד רגע לא יהיו בארץ מפתחי תוכנה כי הכל יעבור להודו. עברו כמעט 20 שנה ואנחנו עוד פה ועדיין כותבים קוד. כן וייב קודינג זה מדליק אבל זה רחוק מלאיים על העולם של פיתוח תוכנה.
מה דעתכם? אם הייתם מתחילים היום ללמוד תכנות הייתם מעדיפים לרוץ ולבנות פרויקטים עם AI בתור עובד או ללמוד מה עושה כל שורה עם AI בתור מורה? ולמה?
1 419
לימודי תכנות עם AI - מה יותר קל, מה יותר קשה ולאן זה הולך
חבר שואל - אני רוצה ללמוד תכנות אבל לא בטוח אם יש טעם. אני רוצה לעבוד בפיתוח אבל כל מה שאני רוצה לכתוב ה AI כותב טוב יותר. מי צריך אותי שם בכלל?
בואו נתחיל בלשים את הקלפים על השולחן: ג'מיני יודע היום לכתוב משחק סנייק יותר טוב ויותר מהר ממה שאני כותב. קלוד כתב לי משחק כרטיסיות ללימוד ספרדית עם מנגנון Spaced Repetition כשכל המידע שמור בקבצי טקסט והתרגול משורת הפקודה (ראסט כמובן). לא יהיה הזוי להגיד שכבר היום או בעתיד המאוד קרוב מנועי AI יוכלו לפתור כל תרגיל תכנות של בית ספר יותר טוב מהתלמידים וכנראה גם יותר טוב מהמורה.
עכשיו מה זה אומר עלינו?
מה יותר קל
עברו מזמן הימים בהם אנחנו צריכים להזיע רק כדי שתוכנית תתקמפל, תרוץ או אפילו תחזיר תשובה נכונה. יש לך בעיית קומפילציה? תדביק אותה ל AI ותקבל הסבר מלא לבעיה (כולל אפשרות לשאול שאלות המשך). התוכנית התרסקה בגלל בעיית זכרון? ה AI ימצא לך את הטעות. התוכנית לא החזירה את התשובה הנכונה? ה AI יסביר למה. התוכנית נראית עובדת? בוא ניתן ל AI לקרוא ולוודא שהכל נכון ב 100% ולהציע שיפורים. והדובדבן על הקצפת - אחרי שכבר סיימתי את כל התרגילים אני יכול לבקש מ AI לייצר לי קבוצה נוספת של תרגולים כדי לוודא שהבנתי הכל.
מי שרוצה ללמוד יכול לקבל היום מורה פרטי 24/7 שמסביר כל פרט ומכיר את כל ההיסטוריה. אז נכון מנועי AI (לדעתי) לא מספיק טובים בבניית סילבוס או בהסבר מאפס, אבל בתור מוצר משלים לקורס הם משפרים כל חוויה לימודית.
מה יותר קשה
הבעיה שאנחנו לא מצליחים לעצור שם. החוויה הלימודית האנושית של בוגרי מערכת החינוך היא מרדף אחרי הצלחות קטנות, מרוץ כדי לסמן V ולהצליח במבחן. בעולם של AI זאת בעיה, כי כל תרגיל לימודי שתנסו לעשות ה AI יכול לפתור בשבילכם יותר מהר.
הבעיה הזאת דומה לבעיה של תלמידי יסודי עם מחשבון. הם לא מבינים למה להתאמץ לפתור תרגיל חשבון בלי מחשבון ואנחנו לא מבינים למה להתאמץ לכתוב תוכנית בלי AI. אבל ההבדל הוא תהומי: תלמידי בית ספר יסודי לומדים כי הם יודעים שבמבחן המורה לא תתן להם להשתמש במחשבון, וכי הם עוד קטנים. מי שלומד תכנות חושב על אופציות לתעסוקה וכרגע קשה לו מאוד לראות איזה ערך הוא יקבל מלימודי תכנות. "מה זה מה שאני אעשה כל היום? אגיד ל AI איזה קוד לכתוב?".
ה AI, בכך שהופך את לימודי התכנות להרבה יותר קלים, מוריד משמעותית מהמוטיבציה ללמוד. בגלל שאנחנו לא יודעים איך יראו החיים המקצועיים של מפתחי תוכנה בעוד שנתיים אנחנו מרגישים שקשה להתחייב וללמוד תכנות, שאולי לא יהיה בו צורך.
ובכל זאת
מתכנתים היום שעובדים עם AI מתחלקים ל-2. יש את מי שמשתמש ב AI כדי להשתפר, מבין כל שורה שה AI כותב ודואג שה AI יכתוב את הקוד שהוא היה כותב בעצמו. כן ה AI כותב בדקה משחק סנייק שהיה לוקח לי יום לכתוב וזה מדהים, אבל בגלל שכתבתי כבר משחקי סנייק דומים אני יכול להסתכל על משחק הסנייק שה AI כתב ולוודא שהוא זהה למה שאני הייתי כותב. כן ה AI כותב אלף בדיקות יותר מהר ממה שלוקח לי לשים כוס קפה, אבל מספיק לי לרוץ על הבדיקות האלה בעין כמה דקות כדי להבין אם הוא היה בכיוון או אם צריך להעיף את כל הבדיקות ולייצר אותן מחדש עם פרומפט יותר מדויק.
סוג שני של מפתחים מעדיפים לדלג על הקוד ולהתמקד ב Spec בצד אחד וב Output בצד שני. הם ינסחו יחד עם ה AI מפרט בדיקות מדויק לאותו משחק סנייק ואז יוודאו שהקוד עושה מה שצריך לפי המפרט. הם לא רוצים לדעת מה כתוב שם וממילא מרגישים שזה בזבוז זמן. לא צריך ללמוד מה זה עץ אדום שחור ומתי להשתמש בו במערכת אמיתית, מספיק לאפיין ל AI מה רמת הביצועים הנדרשת ושה AI יבחר איזה מבנה נתונים יתאים.
וצריך להגיד ביושר, אנחנו לא יודעים איך יראו החיים המקצועיים של מפתחי תוכנה בעוד שנתיים, ארבע או שמונה שנים, ואם הם יהיו יותר דומים לתיאור הראשון או השני. כל תיאור מתאים לעבודה אחרת, ואנחנו צריכים לבחור "מה אנחנו רוצים להיות" בלי לדעת אם הדבר שנבחר בכלל יהיה קיים כשנסיים את הלימודים.
ובכל זאת, שני התיאורים כן עוזרים לנו לתכנן קצת יותר בבירור את ההמשך. הנה הנקודות המרכזיות שהייתי משאיר בראש:
1 419
פתרון AoC 2025 יום 5 חידת הטווחים
אתמול כתבתי על בדיקת
cover? של רובי והיום נראה איך היא עוזרת לנו לפתור בעיה אמיתית מתוך Advent Of Code האחרון ואני מתכוון לחלק השני של יום 5. מה שאהבתי בחלק הזה היה שכשניסיתי לקחת את הפתרון של חלק 1 ולהרחיב אותו לחלק 2 זה פשוט לא עבד וכך הבנתי את הטעות שהיתה לי בחלק 1. אבל בואו לא נקדים את המאוחר ונלך לראות את התרגיל.
חלק 1 חיפוש דברים שלא בשום טווח
בחלק הראשון של התרגיל קיבלנו רשימה של טווחים ורשימה של מזהים והיינו צריכים לבצע בדיקת קיום כלומר לראות מי מהפריטים לא נמצא בשום טווח. בגלל ש Range תומך בבדיקת שייכות מהירה זה היה קל ואחרי פענוח הקלט אפשר היה להריץ:
def part1
@to_check.count {|i| @fresh_ids.any? {|r| r.include?(i) } }
end
כאשר @to_check זה רשימת הערכים שצריך לבדוק, @fresh_ids זו רשימת הטווחים ו include? בודק אם טווח מכיל מספר. יש פה לולאה כפולה שרצה על כל אחד מהמספרים לבדוק ובודקת אותו מול כל אחד מהטווחים.
חלק 2 חיפוש כל הדברים
בחלק השני התבקשנו לספור כמה אלמנטים שונים מוכלים בכל הטווחים. לדוגמה עבור רשימת הטווחים:
3-5
10-14
16-20
12-18
התשובה היא 14 כלומר המספרים 3, 4, 5, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19 ו 20.
פה כבר ברור שהלולאה הכפולה לא תעבוד כי בקלט אמיתי יש יותר מדי ערכים לבדוק. גם לפרק את הטווחים למספרים שלהם ולסנן כפולים (הרעיון השני שלי) יהיה בזבזני ולא יצליח להתמודד עם הקלט הארוך. מה עושים? זוכרים שטווחים הם רציפים ולכן המטרה שלנו היא לבנות רשימה של טווחים ללא חפיפות, כלומר את רשימת הטווחים מהדוגמה נרצה להפוך לרשימה:
3-5
10-20
איך עושים את זה? מתחילים עם רשימה ריקה, כל פעם שקוראים טווח מחפשים את כל הטווחים שמתנגשים איתו (או שהם מכילים אותו, או שהוא מכיל אותם, או שהם מכילים את ההתחלה של הטווח החדש או את הסוף שלו), מוחקים אותם ומייצרים טווח חדש שמכיל את כל מה שמחקנו ואת הדבר החדש. חוזרים על זה בלולאה עד שכיסינו את כל הטווחים. זה הקוד:
def add_range(r)
affected, unaffected = ranges.partition do |range|
range.cover?(r) || r.cover?(range) || range.cover?(r.begin) || range.cover?(r.end)
end
self.ranges = unaffected
new_range = ([*affected, r].map {|r| r.begin }.min)..([*affected, r].map {|r| r.end }.max)
return add_range(new_range) unless affected.empty?
self.ranges << r
end
אלה מכם שצריכים הסבר נוסף מוזמנים להדביק את הפונקציה למנוע AI לבחירתכם ולבקש הסבר כולל דוגמאות מבטיח שהתוצאה מעניינת.
סך הכל זה הקוד המלא של הפתרון:
require 'set'
class RangeSet
attr_accessor :ranges
def initialize
@ranges = []
end
def add_range(r)
affected, unaffected = ranges.partition do |range|
range.cover?(r) || r.cover?(range) || range.cover?(r.begin) || range.cover?(r.end)
end
self.ranges = unaffected
new_range = ([*affected, r].map {|r| r.begin }.min)..([*affected, r].map {|r| r.end }.max)
return add_range(new_range) unless affected.empty?
self.ranges << r
end
end
class Day5
attr_accessor :fresh_ids, :to_check
def initialize(fname)
fresh_ids, to_check = File
.read(fname)
.lines
.map {|l| l.strip }
.slice_when {|e1, e2| e1.empty? }
.to_a
@fresh_ids = fresh_ids.reject(&:empty?).map do |r|
r.split("-").map(&:to_i).then {|a| Range.new(*a) }
end
@to_check = to_check.map(&:to_i)
end
def part1
@to_check.count {|i| @fresh_ids.any? {|r| r.include?(i) } }
end
def part2
stock = RangeSet.new
@fresh_ids.each {|r| stock.add_range(r) }
stock
end
end
if $PROGRAM_NAME == __FILE__
d = Day5.new("input.txt")
pp d.part1
pp d.part2.ranges.map {|r| r.size }.sum
end1 419
פתרון AoC 2025 יום 5 חידת הטווחים
אתמול כתבתי על בדיקת
cover? של רובי והיום נראה איך היא עוזרת לנו לפתור בעיה אמיתית מתוך Advent Of Code האחרון ואני מתכוון לחלק השני של יום 5. מה שאהבתי בחלק הזה היה שכשניסיתי לקחת את הפתרון של חלק 1 ולהרחיב אותו לחלק 2 זה פשוט לא עבד וכך הבנתי את הטעות שהיתה לי בחלק 1. אבל בואו לא נקדים את המאוחר ונלך לראות את התרגיל.
חלק 1 חיפוש דברים שלא בשום טווח
בחלק הראשון של התרגיל קיבלנו רשימה של טווחים ורשימה של מזהים והיינו צריכים לבצע בדיקת קיום כלומר לראות מי מהפריטים לא נמצא בשום טווח. בגלל ש Range תומך בבדיקת שייכות מהירה זה היה קל ואחרי פענוח הקלט אפשר היה להריץ:
def part1
@to_check.count {|i| @fresh_ids.any? {|r| r.include?(i) } }
end
כאשר @to_check זה רשימת הערכים שצריך לבדוק, @fresh_ids זו רשימת הטווחים ו include? בודק אם טווח מכיל מספר. יש פה לולאה כפולה שרצה על כל אחד מהמספרים לבדוק ובודקת אותו מול כל אחד מהטווחים.
חלק 2 חיפוש כל הדברים
בחלק השני התבקשנו לספור כמה אלמנטים שונים מוכלים בכל הטווחים. לדוגמה עבור רשימת הטווחים:
3-5
10-14
16-20
12-18
התשובה היא 14 כלומר המספרים 3, 4, 5, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19 ו 20.
פה כבר ברור שהלולאה הכפולה לא תעבוד כי בקלט אמיתי יש יותר מדי ערכים לבדוק. גם לפרק את הטווחים למספרים שלהם ולסנן כפולים (הרעיון השני שלי) יהיה בזבזני ולא יצליח להתמודד עם הקלט הארוך. מה עושים? זוכרים שטווחים הם רציפים ולכן המטרה שלנו היא לבנות רשימה של טווחים ללא חפיפות, כלומר את רשימת הטווחים מהדוגמה נרצה להפוך לרשימה:
3-5
10-20
איך עושים את זה? מתחילים עם רשימה ריקה, כל פעם שקוראים טווח מחפשים את כל הטווחים שמתנגשים איתו (או שהם מכילים אותו, או שהוא מכיל אותם, או שהם מכילים את ההתחלה של הטווח החדש או את הסוף שלו), מוחקים אותם ומייצרים טווח חדש שמכיל את כל מה שמחקנו ואת הדבר החדש. חוזרים על זה בלולאה עד שכיסינו את כל הטווחים. זה הקוד:
def add_range(r)
affected, unaffected = ranges.partition do |range|
range.cover?(r) || r.cover?(range) || range.cover?(r.begin) || range.cover?(r.end)
end
self.ranges = unaffected
new_range = ([*affected, r].map {|r| r.begin }.min)..([*affected, r].map {|r| r.end }.max)
return add_range(new_range) unless affected.empty?
self.ranges << r
end
אלה מכם שצריכים הסבר נוסף מוזמנים להדביק את הפונקציה למנוע AI לבחירתכם ולבקש הסבר כולל דוגמאות מבטיח שהתוצאה מעניינת.
סך הכל זה הקוד המלא של הפתרון:
require 'set'
class RangeSet
attr_accessor :ranges
def initialize
@ranges = []
end
def add_range(r)
affected, unaffected = ranges.partition do |range|
range.cover?(r) || r.cover?(range) || range.cover?(r.begin) || range.cover?(r.end)
end
self.ranges = unaffected
new_range = ([*affected, r].map {|r| r.begin }.min)..([*affected, r].map {|r| r.end }.max)
return add_range(new_range) unless affected.empty?
self.ranges << r
end
end
class Day5
attr_accessor :fresh_ids, :to_check
def initialize(fname)
fresh_ids, to_check = File
.read(fname)
.lines
.map {|l| l.strip }
.slice_when {|e1, e2| e1.empty? }
.to_a
@fresh_ids = fresh_ids.reject(&:empty?).map do |r|
r.split("-").map(&:to_i).then {|a| Range.new(*a) }
end
@to_check = to_check.map(&:to_i)
end
def part1
@to_check.count {|i| @fresh_ids.any? {|r| r.include?(i) } }
end
def part2
stock = RangeSet.new
@fresh_ids.each {|r| stock.add_range(r) }
stock
end
end
if $PROGRAM_NAME == __FILE__
d = Day5.new("input.txt")
pp d.part1
pp d.part2.ranges.map {|r| r.size }.sum
end1 419
היום למדתי: בדיקת שייכות ל Range ב Ruby
תמיד השתמשתי ב
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).1 419
איך אפשר לפרוץ למישהו לטלגרם ולמה חשוב לשים לב
בהנחה שהאיראנים באמת פרצו רק לטלגרם של בנט בלי לגעת בשאר הטלפון שלו עולה השאלה איך זה עובד, איך אפשר לפרוץ למישהו לחשבון הטלגרם, איך אני שומר שלא יפרצו לי לחשבון הטלגרם ומה עושים כדי לצמצם נזקים אם פריצה התרחשה. אני כמובן לא יודע כלום על מה באמת קרה בין האיראנים לבנט אבל אפשר לדמיין תרחישים:
1. השתלטות זמנית על מספר טלפון - הדרך הכי קלה לפרוץ לטלגרם או לכל שירות שמתבסס על אימות דרך מספר טלפון היא להשיג גישה זמנית למספר הטלפון של הקרבן או לסמסים שמגיעים אליו. זה יכול להיות דרך פריצה לחברה סלולארית או לאיזושהי אנטנת שידור בדרך, זה יכול להיות זיוף של בקשת ניוד מספר, אובדן סים או דיווח על גניבה. לדוגמה אני מתקשר לסלקום, מזדהה בתור בנט ומספר שגנבו לי את הטלפון ואני חייב סים חדש אתמול. סלקום שולחים אותי לקנות סים ריק בתחנת שירות ואז אני נותן להם את המספר שכתוב על הסים והם מעבירים את מספר הטלפון שלי לסים החדש והריק. בום, קיבלתי את מספר הטלפון של הקרבן. להערכתי זה תרחיש פחות סביר כי מי שמשיג שליטה על מספר טלפון לא יעצור בהשתלטות רק על הטלגרם.
2. השתלטות על סשן פעיל - אם לבנט היה טלגרם מחובר על המחשב וגם על הטלפון, ואולי גם על טלפון ישן, אפשר לגנוב את אחד המכשירים האחרים המחוברים לטלגרם ודרכו להגיע לכל השיחות ואנשי הקשר בטלגרם. זה לא מאוד מסתדר עם הדיווח שהדבר היחיד שנפרץ היה הטלגרם אבל אף פעם אי אפשר לדעת.
3. השתלטות על הטלפון עם רוגלה - אם התקינו רוגלה על הטלפון של בנט האיראנים יכלו כמובן להגיע לטלגרם שלו כמו גם לכל שירות אחר אליו היה מחובר מהמכשיר. במצב כזה כנראה היינו רואים הדלפה יותר משמעותית עם הקלטות שיחות, תמונות ופרטי חשבון בנק כך שגם זה לא נשמע סביר.
4. שימוש בלקוח טלגרם צד שלישי תמים או מרושע - אם בנט השתמש בתוכנת טלגרם צד שלישי והיא לא שמרה כמו שצריך על פרטי ההתחברות האיראנים יכלו לנצל פרצה באותה תוכנת צד שלישי כדי להגיע לפרטי הכניסה.
5. פריצה לשרתי טלגרם עצמם - אם האיראנים הצליחו להשיג גישה לשרתים של טלגרם עצמה או לשתול קוד זדוני בתוכנת טלגרם או ברור שאפשר היה לנצל את זה כדי להגיע לטלפון של בנט. גם תרחיש זה לא נשמע סביר כי אם כבר יש לך גישה לשרתים של טלגרם אתה תשתמש בזה כדי להעלות גרסה זדונית של האפליקציה שתתקין רוגלה על המכשיר של בנט ולא תעצור בטלגרם.
6. פישינג והנדסה חברתית - קיבלת לינק בטלגרם שאתה מאוד רוצה לפתוח, אתה לוחץ עליו ומגיע למסך שנראה כמו מסך הכניסה של טלגרם ווב אבל מזויף. אתה מתחבר במסך זה ו-בום, התוקף קיבל גישה לחשבון שלך. טלגרם וווטסאפ מאפשרות לנו לסרוק קוד QR כדי להתחבר, כלומר אנחנו נכנסים לאתר טלגרם, שם יש קוד QR ומבקשים מאתנו לפתוח את הטלגרם בטלפון ולסרוק את הקוד כדי להתחבר. עכשיו נניח שבמקום להגיע לאתר טלגרם המקורי הגעתם לאתר טלגרם ווב מזויף. אתם מנסים להתחבר דרך סריקת הקוד אבל למעשה אתם סורקים קוד שהתוקף יצר ואחרי שתסרקו אותו אתם תחברו את התוקף לטלגרם שלכם. או - נכנסתם לאתר כדי להתחבר לטלגרם ווב, הם כותבים שהם שלחו קוד לטלגרם בטלפון שלכם ואתם מכניסים את הקוד באתר אבל בעצם הייתם באתר מתחזה והקוד שלכם הגיע לתוקף שמשתמש בו כדי להתחבר בשמכם לטלגרם. אם הסיפור של בנט נכון ורק הטלגרם נפרץ אז התרחיש הזה הוא התרחיש הסביר ביותר.
בקבוצה של טוקוד כמעט כל יום חשבון טלגרם פרוץ מפרסם איזושהי הודעת ספאם. החשבונות הפרוצים האלה היו שייכים פעם לאנשים אמיתיים (אולי עדיין). מתקפות הנדסה חברתית וזיופים בטלגרם הפכו לצערנו למאוד נפוצות והופכות את החיים בטלגרם למעייפים יותר. אני מקווה שטלגרם תמצא דרך לגרש את הסקאמרים ולסנן את המתקפות. עד אז היו זהירים, שימו לב לא להכנס ללינקים חשודים ולא להתחבר לטלגרם דרך מסכי כניסה מפוקפקים.
1 419
גם בלי לקרוא לעומק אנחנו רואים שההערות נמחקו. ב JavaScript המידע עדיין מועתק אבל בלי ההסבר שהיה בהערה בפייתון. מי שרק מסתכל על השורה הזו ב JavaScript אולי לא מבין מה מטרתה:
this.events.push(["start", [token.name, { ...(token.attrs || {}) }]]);
} else {
יותר מזה גם את הלוגיקה של open_elements ה AI מחק לגמרי וכעת משתנה זה נשאר בתור משתנה של המחלקה בלי שימוש אמיתי שם.
אבל הבדיקות עברו...
אני שומע אתכם. סט של 9,200 בדיקות חייב לשכנע אותנו שהספריה תואמת "100%" מבחינת התנהגות לספריה המקורית. וזה בדיוק מה שהופך את הדוגמה הזאת למוצלחת כדי שנשים לב שקוד הוא לא רק התוצאה שלו. ההערות, ההכנה שאנחנו בונים פנימה לשינויים עתידיים, אפילו הבחירה בשמות מסוימים ובסגנון כתיבה מסוים, כל אלה משפיעים על איכות המערכת. משפיעים על היכולת לתחזק, לשפר ולהרחיב אותה מחר ובהמשך.
הבעיה כאן היא לא ש AI תרגם את הקוד. כמו ש AI תרגם את הקוד הוא היה יכול לתרגם גם את ההערות ולשמור על הרוח המקורית והקריאות של הספריה המקורית. הבעיה היא של AI אין "טעם", אין את החוויה האנושית של לקרוא קוד ולחשוב האם זה קוד הספריה שאני רוצה, האם הקוד הזה מבטא את הידיעות והניחושים שלי לגבי העתיד של הספריה.
הבעיה היא לא ש AI כתב את הקוד אלא שאף אחד לא קרא את הקוד והתלבט בכל השאלות שהטרידו את היוצר של ספריית הפייתון המקורית. הכל בסדר ש AI יכתוב קוד כל עוד יש בסוף בן אדם שקורא אותו.1 419
תשעת אלפים בדיקות עברו
סיימון ווילסון נתן ל 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]);
}
}
Вже доступно! Дослідження Telegram за 2025 — головні інсайти року 
