ToCode
1.42K subscribers
3.83K links
טיפים קצרים למתכנתים מאת ינון פרק
Download Telegram
לכאורה אם connect מחזיר 0 לא חיברנו כלום ולכן מספר החוטים לא ירד. זה נכון ותואם את שמות המשתנים אבל לא תואם את הגדרות השאלה. המשימה שלי היתה לבצע אלף איטרציות בלי קשר לכמות החיבורים שבוצעו או לא בוצעו. אני מבין למה קלוד חושב שמשהו פה מוזר וטוב שהוא מפנה את תשומת לבי לבחירת השמות הלא נכונה אבל נשים לב שאנחנו צריכים להיות מאוד זהירים בקריאת הערות Code Review שמגיעות מ AI כי בלי הבנה קשה לתת פידבק מועיל.

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

  def part2
i = 0
while @circuits.values.uniq.size > 1
next_p, next_q = @distances[i]
connect(next_p, next_q)
i += 1
pp "#{@circuits.values.uniq.size} circuits remaining"
end
next_p[0] * next_q[0]
end


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

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

1. כל נקודה מיוצגת על ידי צומת.

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

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

זה הקוד של המחלקה Circuit שמנהלת את העץ:

class Circuit
attr_accessor :parent, :name

def initialize(name)
self.name = name
self.parent = self
end

def merge(other)
self.parent = self.parent.parent while self.parent.parent != self.parent
other.parent = other.parent.parent while other.parent.parent != other.parent

return 0 if self.parent == other.parent

c = Circuit.new("#{self.name}<->#{other.name}")
self.parent.parent = c
other.parent.parent = c
return 1
end
end


וקוד הפתרון בעזרת Circuit הוא עכשיו:

def part2
@circuits = @circuits.map {|k, v| [k, Circuit.new(k.to_s)] }.to_h
cables = @circuits.size - 1

while cables > 0
next_p, next_q = @distances.shift
cables -= @circuits[next_p].merge(@circuits[next_q])
pp "connected #{next_p[0]}, #{next_q[0]}. #{cables} cables remaining"
end
next_p[0] * next_q[0]
end


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

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

הדילמה פה ברורה ויושבת בגדול בין שלוש אפשרויות:

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

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

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

שלושת האפשרויות גרועות.

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

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

וכן זאת השקעה שדורשת זמן. וכן אני יודע שכולם צריכים תשובה עכשיו והקוד מחכה והמשקיע מחכה והבוס מחכה והלוואי והיה פתרון קל. החכם אומר שהזמן הכי טוב לנטוע עץ היה לפני עשרים שנה. עכשיו זה רק הזמן השני הכי טוב.
1
חדשנות, ספריות, AI
ספריות חיצוניות במידה רבה וקוד תשתית פנימי במידה יותר קטנה קובעים את הגבולות של האפשרי. הם החוזה שלנו עם העולם. וכן אפשר להפר חוזה אבל לפעמים יש קנס. ניקח ספריית רובי בשם acts-as-taggable-on בשביל הדוגמה:

https://github.com/mbleigh/acts-as-taggable-on

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

@another_user.skill_list.add("clowning")


אבל אם נכתוב:

all_users.each do |u|
u.skill_list.add("clowning")
u.save
end


נבצע המון פעולות insert וזה כנראה יעלה בביצועים.

כמה מחשבות סביב זה בעקבות התנגשויות בין פיצ'רים:

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

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

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

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

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

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

def create_files(n, prefix):
for i in range(n):
with open(f"{prefix}{i}", "a") as f:
pass


עכשיו נשפר את הפונקציה כדי לאפשר למי שקורא לה להעביר תבנית מלאה לשם קובץ. לא רק תחילית אלא גם סיומת כדי שהקבצים יהיו למשל demo1.txt, demo2.txt.

פתרון 1 - העברת פונקציה
בפוסט קודם בנושא הצעתי להעביר פונקציה עבור prefix וזה נראה כך:

def create_files(n, filename_template):
for i in range(n):
filename = filename_template(i)
with open(filename, "a") as f:
pass


create_files(5, lambda i: f"demo{i}.txt")


זה עובד אבל קצת מסורבל ומכריח את כל מי שמפעיל את הפונקציה לדעת מה זה lambda.

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

def create_files(n, filename_template):
for i in range(n):
filename = filename_template.format(i=i)
with open(filename, "a") as f:
pass


create_files(5, "demo{i}.txt")


הרבה יותר קל גם למי שמפעיל את הפונקציה וגם למי שכותב אותה.

נ.ב. בטלגרם עניתי שאפשר אולי להשתמש ב t-strings כדי לכתוב את התבנית אבל האמת שזאת היתה טעות. format קל יותר, נכון יותר למצב הזה ועובד. מנגנון ה t-string החדש של פייתון 3.14 מבצע את השערוך בזמן יצירת המחרוזת בדיוק כמו f-string ולכן לא היה עובד כאן.
👍2
טיילווינד. AI. שינויים.
טיילווינד הפך בשנים האחרונות מרעיון הזוי לדרך הסטנדרטית בה אנשים כותבים CSS. אם אתם מפתחי ווב בוודאות נתקלתם בו וכנראה גם כתבתם בו. כשאדם וותן התחיל את טיילווינד הוא בנה ספריית תבניות שמדגימה איך כדאי להשתמש בטיילווינד כדי לבנות דברים יפים. הספריה נקראה tailwind ui היום היא נקראת tailwind plus והמכירות שלה עוזרות לאדם להחזיק את העסק ולהמשיך לפתח את הפריימוורק.

ואז הגיע AI.

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

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

> I totally see the value in the feature and I would like to find a way to add it.

> But the reality is that 75% of the people on our engineering team lost their jobs here yesterday because of the brutal impact AI has had on our business. And every second I spend trying to do fun free things for the community like this is a second I'm not spending trying to turn the business around and make sure the people who are still here are getting their paychecks every month.

> Traffic to our docs is down about 40% from early 2023 despite Tailwind being more popular than ever. The docs are the only way people find out about our commercial products, and without customers we can't afford to maintain the framework. I really want to figure out a way to offer LLM-optimized docs that don't make that situation even worse (again we literally had to lay off 75% of the team yesterday), but I can't prioritize it right now unfortunately, and I'm nervous to offer them without solving that problem first.

> @PaulRBerg I don't see the AGENTS.md stuff we offer as part of the sponsorship program as anything similar to this at all — that's just a short markdown file with a bunch of my own personal opinions and what I consider best practices to nudge LLMs into writing their Tailwind stuff in a specific way. It's not the docs at all, and I resent the accusation that I am not disclosing my "true intentions" here or something.

> @mtsears4 Tailwind is growing faster than it ever has and is bigger than it ever has been, and our revenue is down close to 80%. Right now there's just no correlation between making Tailwind easier to use and making development of the framework more sustainable. I need to fix that before making Tailwind easier to use benefits anyone, because if I can't fix that this project is going to become unmaintained abandonware when there is no one left employed to work on it. I appreciate the sentiment and agree in spirit, it's just more complicated than that in reality right now.

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

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

2. אפשרות שניה הבדיקה תגיד "צור ויקי עם שלושה דפים X, Y ו Z. חפש X. תראה שהתוצאה הראשונה היא X."

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

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

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

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

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

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

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

1. תריץ Code Review על הקומיט האחרון.

2. תסביר מה עושה פונקציה.

3. תכתוב תוכנית בדיקות לפונקציה.

4. תזהה מה המסלול בקוד שגורם לבאג לפי תיאור שהגיע מהתמיכה.

5. נתונה בדיקה שנכשלת, תסביר למה היא נכשלת ותתקן אותה.

6. תעשה Refactor כדי לנקות קוד כפול.

כל אחד מהסעיפים פה יכול לתת תוצאה משמעותית יותר טובה ככל שהפרומפט יהיה טוב יותר אבל ככל שאנחנו מפעילים משהו יותר פעמים כך אנחנו מתעייפים לכתוב את הפרומפט המלא. מה עושים? ב VS Code אפשר ליצור קובץ פרומפט:

1. לוחצים Ctrl+Shift+P ובוחרים Chat: New prompt file

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

3. בוחרים שם לקובץ הפרומפט.

4. כותבים את הפרומפט בקובץ ושומרים.

הפרומפטים נשמרים בתיקיית github/prompts עם סיומת .prompt.md. אחרי שיצרתם קובץ פרומפט אתם יכולים להפעיל אותו מתוך הצ'אט באמצעות כתיבת / ואז שם קובץ הפרומפט (לא צריך את כל השם יש השלמה אוטומטית).

זו דוגמה שראינו לפרומפט שמסביר פונקציה בתוכנית c בפרויקט libuv:

---
agent: agent
---

Please analyze the code below and provide a report with the following sections:

* 1. Functional Analysis *
* **Input Arguments:** detailed breakdown of types, expected ranges, and **ownership semantics** (who frees the memory?). Check for \const\ correctness.
* **Return Values:** Explain all possible return states, including specific error codes, \NULL\ pointers, or \errno\ implications.
* **Intended Use Case:** Infer who calls this function and why, based on the logic (e.g., is it a hot-path utility, a setup function, a blocking I/O call?).

* 2. Critical Code Review (The "Roast") *
* **Edge Cases & Validation:** Does the function handle \NULL\ pointers, zero-length buffers, or integer overflows?
* **Undefined Behavior (UB):** Identify any potential UB (e.g., signed overflows, uninitialized memory, strict aliasing violations).
* **Security:** specific checks for buffer overflows, use-after-free, double-free, or information leaks.

* 3. Performance & Resources *
* **Complexity:** Time and Space complexity (Big O).
* **System Impact:** specific analysis of heap allocations (\malloc\), blocking system calls, or cache locality issues.
* **Concurrency:** Is this function thread-safe? Is it reentrant?

* 4. Refactoring Suggestions *
1
*   Provide a refactored version of the code that fixes identified issues while maintaining the API signature (if possible).


קבצי Instructions
אם יש תוכן שמשותף לכמה פרומפטים ואתם רוצים לכתוב אותו תמיד כשפונים ל AI עבור קבצים מסוימים או קבצים בפרויקט מסוים תוכלו ליצור קובץ instructions:

1. לוחצים Ctrl+Shift+P ובוחרים Chat: New instructions file

2. בוחרים איפה לשמור את הקובץ - אצלכם במשתמש או בפרויקט.

3. בוחרים שם לקובץ.

4. מקלידים את ההוראות ושומרים.

קבצי ההוראות נשמרים בתיקיית instructions עם סיומת .instructions.md. בוובינר ראינו דוגמה לקובץ הוראות לפרויקט C:

---
applyTo: '*.*'
---
Act as a Senior C Systems Engineer and Security Researcher. You're working on a mission-critical library used by many external clients. Reliability, security (memory safety), and high performance are paramount.


כתיבת סוכנים
ל VS Code יש 4 סוכנים מובנים: Agent, Ask, Edit ו Plan. סוכן הוא חיבור בין קובץ הוראות לרשימת כלים שהוא יכול להפעיל - ואנחנו יכולים ליצור סוכנים חדשים למשימות ספציפיות. לדוגמה כדאי לנו לכתוב סוכן שיודע לעשות Code Review ומכיל כבר את תהליך העבודה וההרשאות הספציפיות הנדרשות לביצוע Code Review.

בשביל ליצור קובץ סוכן חדש אני בוחר מתפריט ה Ctrl+Shift+P באופציה New Custom Agent, שוב בוחר בשבילו מיקום וכותב את התוכן בקובץ. לסוכן יש גם הוראות וגם רשימה של כלים ומסך העריכה של VS Code כולל תיבת בחירה כדי לבחור את הכלים. הקובץ עצמו נשמר בתיקיית github/agents בפרויקט עם הסיומת .agent.md. זו דוגמה שראינו לקובץ סוכן שעושה Code Review:

---
name: Code Reviewer
description: An agent that reviews code for quality, style, security, and best practices.
tools: ['vscode', 'read', 'search', 'web', 'agent', 'todo', 'execute']
---
As a "Code Reviewer" agent, your primary function is to meticulously examine provided code snippets or files. Focus on identifying potential bugs, security vulnerabilities, adherence to project coding standards, and overall code quality. Provide actionable feedback and suggest improvements as comments directly in the code or a concise list of recommendations. You must be thorough.

As a "Code Reviewer" agent, your primary function is to meticulously examine provided code.

MANDATORY PROCESS:
1. Run the tests with "npm run test"
2. Perform static code review

If tests cannot be run, explicitly state why.


וזה היה סוכן שיודע רק להריץ את הבדיקות:

---
name: Test Runner
description: A subagent responsible for executing the Vitest test suite, analyzing failures, detecting flaky tests, and flagging potential performance regressions.
tools: ['execute']
---
As a Test Runner agent, your task is to run the project's Vitest test suite
and report results back to the invoking agent.

Responsibilities:
- Execute Vitest with "npm run test"
- Summarize failures with likely causes
- Identify slow or flaky tests
- Call out tests that may mask performance issues
- Do NOT modify code unless explicitly asked

Output should be concise, structured, and suitable for inclusion in a
code review.


אחרי שאני יוצר את הסוכנים המותאמים אישית אני יכול להפעיל אותם מחלון הצ'אט באמצעות שינוי הסוכן הפעיל ואתם תראו אותם מתחת ל-4 הסוכנים המובנים ב VS Code.

הרצת Sub Agents
לפעמים נרצה לבקש מהסוכן לעשות משהו בלי למלא את הקונטקסט בכל התוכן של מה שבוצע ובעצם להסתכל רק על התוצאה של תהליך ארוך. מנגנון כזה נקרא Sub Agents ובוובינר עצמו קצת הסתבכתי איתו. ניסיתי ליצור סוכן Code Review שיפעיל בתור Sub Agent סוכן אחר של Test Runner. גיליתי אחרי הוובינר שבשביל לעשות את זה צריך להפעיל בהגדרות ה VS Code את האופציה Custom Agent In Subagent. אחרי הפעלת האופציה הזו ויציאה מ VS Code והפעלה מחדש הצלחתי להפעיל סוכן של Code Review שיפעיל Sub Agent של Test Runner.

בשביל להפעיל משימה מסוימת בתור Sub agent עלינו לבקש את זה מהסוכן בקובץ ההוראות. בדוגמה הבאה אני מבקש מסוכן ה Code Review לבצע את הרצת הבדיקות בתור סוכן צדדי:

---
name: Code Reviewer
description: An agent that reviews code for quality, style, security, and best practices.
tools: ['vscode', 'read', 'search', 'web', 'agent', 'todo']
---
As a "Code Reviewer" agent, your primary function is to meticulously examine provided code snippets or files. Focus on identifying potential bugs, security vulnerabilities, adherence to project coding standards, and overall code quality. Provide actionable feedback and suggest improvements as comments directly in the code or a concise list of recommendations. You must be thorough.

As a "Code Reviewer" agent, your primary function is to meticulously examine provided code.

MANDATORY PROCESS:
1. Use a sub agent to run the tests with "npm run test"
2. Perform static code review

If tests cannot be run, explicitly state why.


שימו לב לשורה:

1. Use a sub agent to run the tests with "npm run test"


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

Since I don't have terminal access to run git commands directly, let me check if there are any staged or unstaged changes first, and also look at the workspace to understand what might have changed recently.

Actually, let me start by:

Running the test suite via subagent (as required)
Then look at what files might be the focus of the latest work
Let me proceed with the mandatory process.


היתרון במבנה זה הוא שאני לא חייב לתת לסוכן ה Code Review את כלי הטרמינל ואני יכול להפריד ולהפעיל כל סוכן עם הכלים שרלוונטים לו.

התקנת שירותי MCP
בוובינר לא הספקנו לדבר על MCP אבל אני כן רוצה לגעת בזה בסיכום. בפרויקט אני יכול ליצור תיקיה בשם .vscode ובתוכה קובץ בשם mcp.json שיכיל את הגדרות שרתי ה MCP של הפרויקט הספציפי. אפשר להגיע לקובץ הזה מתוך Ctrl-Shift-P ולבחור Open Workspace Folder MCP Configuration.

הגדרת שרתי MCP דורשת קצת יותר מחשבה על הארגון ואופן העבודה שלנו עם אנשים אחרים:

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

2. שרת MCP שמוגדר בפרוטוקול http יותר גמיש ויעבוד לכל מי שיפתח את הפרויקט.

לדוגמה אם אני רוצה להוסיף את שרת ה MCP של גיטהאב כדי שאפשר יהיה מתוך VS Code להסתכל על Issues ולבצע פעולות ישירות בגיטהאב אני אכתוב בקובץ:

{
"servers": {
"github": {
"type": "http",
"url": "https://api.githubcopilot.com/mcp/"
}
}
}


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

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

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

import { useState, useEffect, useEffectEvent } from 'react'
import './App.css'

function EffectEventDemo({name}: {name: string}) {
const [time, setTime] = useState(0);

useEffect(() => {
const ivl = setInterval(() => {
setTime(c => c + 1)
}, 1000);

return () => clearInterval(ivl);
}, [name]);

return <p>User {name} is online for {time} seconds</p>
}

function App() {
return (
<EffectEventDemo name='demo' />
)
}

export default App



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

function EffectEventDemo({name}: {name: string}) {
const [time, setTime] = useState(0);

useEffect(() => {
const ivl = setInterval(() => {
fetch(\/ping\, {
method: 'POST',
contentType: 'application/json',
body: JSON.stringify({
name,
time,
}),
});
setTime(c => c + 1)
}, 1000);

return () => clearInterval(ivl);
}, [name]);

return <p>User {name} is online for {time} seconds</p>
}


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

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

function EffectEventDemo({name}: {name: string}) {
const [time, setTime] = useState(0);
const notifyServer = useEffectEvent(() => {
fetch(\/ping\, {
method: 'POST',
contentType: 'application/json',
body: JSON.stringify({
name,
time,
}),
});
});

useEffect(() => {
const ivl = setInterval(() => {
notifyServer();
setTime(c => c + 1)
}, 1000);

return () => clearInterval(ivl);
}, [name]);

return <p>User {name} is online for {time} seconds</p>
}


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

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

function EffectEventDemo({name}: {name: string}) {
const [time, setTime] = useState(0);
useEffect(() => {
fetch(\/ping\, {
method: 'POST',
contentType: 'application/json',
body: JSON.stringify({
name,
time,
}),
});
}, [time, name]);

useEffect(() => {
const ivl = setInterval(() => {
notifyServer();
setTime(c => c + 1)
}, 1000);

return () => clearInterval(ivl);
}, [name]);

return <p>User {name} is online for {time} seconds</p>
}
זה עובד אבל החיסרון כאן הוא שאין לנו שליטה על תדירות עדכון השרת. אנחנו מושפעים מהשינויים ב name וב time.

אפשרות שניה היא לשמור את זמן החיבור ואולי את name מחוץ לקומפוננטות ולכתוב את כל קוד עדכון השרת במקום אחר. זאת הגישה שלוקחת react-query. הקוד בגדול היה נראה כך (נכתב על ידי ChatGPT):

import { useEffect, useState } from "react";
import { useQuery } from "@tanstack/react-query";
import { sendOnlineTime } from "./api";

type Props = {
name: string;
};

export function UserOnlineTimer({ name }: Props) {
const [time, setTime] = useState(0);

// Local timer (increments every second)
useEffect(() => {
const interval = setInterval(() => {
setTime((t) => t + 1);
}, 1000);

return () => clearInterval(interval);
}, []);

// React Query handles the polling request
useQuery({
queryKey: ["user-online", name, time],
queryFn: () => sendOnlineTime(name, time),
enabled: time > 0,
refetchInterval: 1000,
refetchIntervalInBackground: true,
});

return <p>User {name} is online for {time} seconds</p>;
}


הפעם זה מבנה טוב ואפילו את הקריאה ל useQuery אפשר להוציא ל Custom Hook מה שעוד יותר יקל עלינו בפיתוח קומפוננטות "מדווחות" בהמשך.

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

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

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

ברור שזה לא שפת תכנות מסוימת או אפילו צורת כתיבת קוד מסוימת. מפתחים שכתבו משחקים למחשב אישי בשנות ה 80 לא ימצאו את הידיים והרגליים בסטודיו לפיתוח משחקים של 2025. אפילו בפערים קטנים בהרבה הקושי בולט, מפתחי PHP שרק בשנת 2004 בנו את כל האינטרנט התקשו ב 2014 למצוא עבודה בפיתוח אותה אינטרנט כשהפרדיגמה השתנתה ועברנו לכתוב קוד Front End.

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

1. היכרות עם אוסף גדול של פתרונות.

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

3. תשומת לב לפרטים ויכולת לזהות את הבעיה האמיתית ואת הבעיות האמיתיות העתידיות.

4. טעם טוב ויכולת לראות כשמשהו "לא נכון" או "יסבך אותנו בהמשך" או אפילו פשוט "מכוער".

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

6. יצירתיות, תעוזה וחשיבה מחוץ לקופסה. בתכנות זאת הראיה של "אם היה לי X אז הבעיה שאני עכשיו מסתכל עליה בכלל לא היתה קיימת. בעצם צריך לבנות את X".

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

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

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

1. כתוב סקריפט פייתון שמקבל שם קובץ ומדפיס את מספר השורות בקובץ.
2. אני תלמיד בחטיבה וקיבלתי שיעורי בית: כתוב סקריפט פייתון שמקבל שם קובץ ומדפיס את מספר השורות בקובץ. תוכל לעזור לי לפתור כדי שאקבל 100 על המשימה?
3. כתוב סקריפט פייתון שמקבל קובץ (מקומי או מרוחק) ומדפיס את מספר השורות בקובץ.
4. כתוב סקריפט פייתון שסופר שורות בקובץ. אנחנו מצפים להפעיל את הסקריפט על קבצים ענקיים שים לב לביצועים ולזכרון.
5. כתוב סקריפט פייתון שסופר שורות בקובץ טקסט. יש סיכוי טוב שבעתיד יבקשו גם לספור שורות תוכן בקובץ אקסל או CSV כלומר בלי הכותרת.