ToCode
Ir al canal en Telegram
1 417
Suscriptores
-224 horas
-37 días
-630 días
Archivo de publicaciones
1 417
פרויקט אישי ומתוחזק הוא לא רק עוד דרך להתמקצע, הוא הבסיס עליו תוכלו לנסות טריקים חדשים בלי לדאוג ובלי לבקש רשות.
ומה לגבי קבלה לעבודה?
אנחנו נמצאים בתקופה משוגעת ואני מודה שקשה לדעת מה ילד יום. יש סיכוי שבשבועות או חודשים הקרובים הוספת דף פרופיל עם "פרויקטים" יהפוך לסטנדרט בכל קורות חיים וכל אחד פשוט יוסיף פרויקט קטן שהוא כתב ב base44 לקורות חיים, והרמה של הפרויקטים האוטומטיים האלה תהיה כל כך גבוהה שאי אפשר יהיה להבדיל בינם לבין פרויקט אמיתי.
אני לא יודע.
כרגע אנחנו עוד לא שם. נכון להיום כשאני מסתכל על קוד שנכתב על ידי Vibe Coding יש בו בעיות ואם הייתי צריך לבחון קוד כזה בקורות חיים מהר מאוד זה היה נראה לי חשוד. בשביל להפעיל היום AI ולקבל תוצאה של קוד ברמה גבוהה צריך להכיר את החומר המקצועי ברמה טובה, ולכן לדעתי זה לא מאוד משנה. מנהלי פיתוח שיקראו את הקוד של הפרויקט יבינו מה הרמה המקצועית של המפתח, גם אם הוא נעזר ב AI, ולמעשה אני חושב שרמה מקצועית נמוכה זה משהו שרואים אפילו יותר בבירור כשאנשים נעזרים ב AI.
לכן להערכתי פרויקט אישי ברמה גבוהה עדיין יכול לפתוח דלתות. בניגוד להערכה שלי על התמקצעות פה אני שם כוכבית בגלל אי הוודאות סביב העתיד.
מסלול ליווי לפיתוח פרויקטים
אז מה יהיה לנו ביוני-יולי? אלה עיקרי הדברים:
1. שלושה שבועות של איפיון פרויקט, סגירה טכנולוגית, כתיבת POC וכל המחקר שמסביב. הכל בהנחיה מלאה ועם פידבק מפורט על העבודה שלכם כדי להגיע לאיפיון הכי טוב שאפשר ולהיחשף לטכנולוגיות חדשות שאולי לא התנסיתם בהן עדיין.
2. חמישה שבועות של פיתוח לפי תוכנית מפורטת ואישית שאני אעזור לכם לבנות. התוכנית בנויה על יעדים שבועיים ויש לכם גמישות לעבוד בזמן שנוח לכם.
3. מפגש זום פעם בשבוע בו נראה נקודות מרכזיות מהפרויקט שלכם ומהפרויקטים של חברים לקבוצה, נדבר על אתגרים מרכזיים שעלו ואיך פתרנו אותם (הכל מוקלט).
4. שיעור שבועי על נושא משותף לכולם כמו ביצועים, אבטחת מידע, ארכיטקטורה, כלי AI ועוד.
אם כבר יש לכם רעיון לפרויקט יכולים לרשום אותו בדף הרישום כאן:
https://www.tocode.co.il/mentoring
אם אתם רוצים להשתתף אבל אין רעיון או שלא מספיק סגורים, או אפילו רוצים לשמוע עוד פרטים על המסלול - תשאירו לי הודעה ונתאם שיחת ייעוץ מסודרת.
1 417
אחרי פסח הגיע - בואו נדבר על פרויקט תוכנה
האם פיתוח פרויקט תוכנה אישי הוא עדיין דרך מומלצת להתקדם בתור מפתחים?
תכף נרחיב על השאלה הזאת אבל לפני שאשכח, אם תסכימו איתי שהתשובה חיובית (ואני מקווה שתסכימו), ביוני הקרוב אני פותח קבוצה נוספת במסלול פיתוח הפרויקטים, בה תקבלו את המסגרת והכלים לבנות פרויקט תוכנה אישי שלכם ברמה גבוהה ועם שילוב כלי AI וכל מתודולוגיות הפיתוח העדכניות. אם יש לכם רעיון לפרויקט ואתם מחפשים הזדמנות להוציא אותו מהמגירה אני שלכם ביוני ויולי לתת את הדחיפה ולעזור בכל מה שאפשר. פרטים כאן:
https://www.tocode.co.il/mentoring
ועכשיו לפוסט.
האם פיתוח פרויקט אישי הוא עדיין הדרך להתקדם בתור מפתחים?
לשאלה הזאת תמיד היו טיעונים טובים לשני הצדדים. מצד אחד בעבודה על פרויקט אנחנו מתעסקים בהמון דברים: צריך גם לכתוב את ה Backend וגם את ה Frontend, גם להתאים למובייל וגם לדאוג לביצועים, גם לכתוב בדיקות וגם להקים תשתית CI/CD, גם להבין איך לכתוב בלי שיפרצו לכם למערכת וגם לכתוב קוד נכון שאפשר יהיה לשנות ולתחזק. איך אפשר למצוא זמן לדעת את כל זה וגם להתמקצע בתחום מסוים? ואם אני לא מתמקצע באף תחום איך אוכל לעבור ראיון עבודה?
מצד שני אנחנו יודעים שפיתוח תוכנה הוא תהליך הוליסטי. ה Backend משפיע על ה Frontend, הבדיקות משפיעות על התחזוקה, אבטחת המידע מושפעת מסגנון הכתיבה. מפתחים שלא מכירים את כל התמונה או שלא מצליחים לראות אותה יפספסו בנקודות קריטיות או שלא יבינו עד הסוף את הקוד שהם כותבים. יותר מזה, עם הכניסה של AI לתמונה אנחנו חוזרים לראות צוותי פיתוח של אחד, מפתח או מפתחת מובילים שנעזרים ב AI כדי לעשות עבודה של 5 אנשים. ההתמקצעות קיימת וקיבלה אופי אחר.
כניסת כלי AI לתמונה הוסיפה עוד נקודת מבט, במיוחד כשאנחנו חושבים על Vibe Coding. אני יכול היום בעזרת Prompt לייצר קוד של מערכת מלאה. כלים כמו Base44 ו Lovable וגם bolt ו v0 ורבים נוספים מעודדים אנשים בלי ידע בפיתוח לבנות מערכות, ופתאום כשאני מוסיף לקורות חיים פרויקט שבניתי זה לא מרשים אף אחד. מי יודע אם באמת כתבתי את זה לבד או שנתתי ל AI לעשות את רוב העבודה והפרויקט מלא באגים או מימושים חלקיים.
למה חשוב שיהיה לכם פרויקט אישי
הרעיון הראשון שכדאי לקחת כאן הוא שהתמקצעות היא לא לינארית ואין לה מדד אחד. הנה כמה דוגמאות למיומנויות שונות שמפתחים יכולים להשתפר בהן:
1. היכולת לקחת משימת פיתוח ולבצע אותה, כלומר לחזור עם קוד שעובד.
2. היכולת לראות מבנה של מערכת ולעבוד בתוך הכללים, כלומר לעדכן את הקוד בלי לשבור הנחות יסוד של המערכת.
3. היכולת לזהות אבסטרקציות וליצור אותן כדי לשפר את השפה של המערכת.
4. היכולת להבין טכנולוגיה חדשה.
5. היכולת לראות מספר פיתרונות של בעיה ולשקול יתרונות וחסרונות של כל גישה.
6. היכולת לראות קדימה ולזהות איך שינוי מסוים ישפיע על המערכת בעתיד או על חלקים אחרים במערכת.
7. היכולת להעריך נכון זמן והיקף עבודה על פיצ'ר מסוים.
8. היכולת לעמוד בזמנים גם על חשבון ביצוע פשרות, וההבנה איזה פשרות אפשר לבצע ועל מה אי אפשר להתפשר.
ומאחר והתמקצעות בפיתוח תוכנה מורכבת מהמון וקטורים שונים, יש גם המון דברים שאפשר לעשות כדי להתמקצע כמפתחי תוכנה. כל הפעילויות האלה חשובות ויעזרו לכם להיות מפתחים טובים יותר:
1. קריאת ספרי תיאוריה במדעי המחשב.
2. פיתרון תרגילים קטנים בטכנולוגיה שאתם לומדים.
3. פיתרון תרגילי אלגוריתמיקה או סיבוכיות.
4. תרומת קוד לפרויקט קוד פתוח (כולל כשהמתחזקים מתלוננים שהקוד שלכם לא מתאים וניהול כל הדיאלוג עד שה PR יתקבל).
5. שיפור ביצועים של מערכת קיימת.
6. זיהוי בעיות אבטחה בקוד שאתם כתבתם או שמישהו אחר כתב.
7. קריאת תיעוד של ספריה, או קורס מקצועי עליה.
8. הוספת פיצ'ר לפרויקט גדול, או תיקון באג בפרויקט כזה.
וכן גם פיתוח פרויקט עצמאי שלכם. פרויקט אישי שמבוצע טוב יכול להיות בסיס טוב לכל סוג התמקצעות שתבחרו - הוא יאפשר לכם ללמוד על אבטחת מידע, על ביצועים, על כתיבת קוד שניתן להרחבה, הוא יאפשר לכם לבעוט בקוד ולראות מה נשבר ואיך לכתוב קוד שיישבר פחות. הוא ייתן לכם מקום להתנסות על האלגוריתמים החדשים שלמדתם או על ספריות חדשות שגיליתם. זה דבר אחד ללמוד איך להקים פונקציית Lambda, וזה דבר הרבה יותר גדול כשפונקציית ה Lambda צריכה להתחבר עם מערכת גדולה שיש לה היגיון פנימי שאתם בונים. אותו דבר לגבי איחסון קבצים על S3, חיבור משתמשים, הטמעת שירות צד-שלישי או כל מנגנון אחר.
1 417
אם לגוגל מותר
אחת מההחלטות שאני לא אוהב ב next היא להכשיל build כשיש שגיאות TypeScript או ESLint. כלומר כן אני מבין את החשיבות, וברור לי למה צריך שיהיו סטנדרטים לפרויקט אבל מצד שני יש תיקונים שאני מעלה כי עכשיו צריך תיקון ואני יודע שעוד יום-יומיים אני מחליף במנגנון יותר טוב או שהם ממש לא חשובים ואני מוכן לחיות עם איזה משתנה שלא השתמשתי בו. ובכלל מי שכל כך חשוב לו ש ESLint יהיה הכרחי יכול תמיד להפעיל את זה עצמאית בקונפיגורציה או דרך Hooks ב git.
בכל אופן עד לאחרונה חשבתי שזו פשוט דעה לא פופולרית שלי, והנה גוגל יוצאים עם IDE חדש משולב AI בשם Firebase Studio, שעושה קסמים עם נקסט 15 ובאמת נראה אחלה ובינתיים גם לא ביקשו עליו כסף, והסטארטר של פרויקט חדש מתחיל עם קובץ הקונפיגורציה שלי:
import type {NextConfig} from 'next';
const nextConfig: NextConfig = {
/* config options here */
typescript: {
ignoreBuildErrors: true,
},
eslint: {
ignoreDuringBuilds: true,
},
};
export default nextConfig;
שני שידרוגים שכן הייתי עושה בסטארטר אגב זה לשדרג את ריאקט לגירסה 19 ואת טיילווינד לגירסה 4, וב IDE עצמו התלונה היחידה שלי היא ששיחה עם ג'מיני לא מתיחסת אוטומטית לקובץ שעכשיו פתוח בעורך.
בכל אופן על חינם אי אפשר להתלונן. אפשר להתחיל לקודד איתו כאן:
https://firebase.studio/1 417
היום למדתי: מילון אוטומטי בפייתון
אני לא בטוח שאשתמש בקוד הבא אבל הוא היה חמוד אז משתף אותו כאן - נניח שיש לכם נקודת קצה בפלאסק שמחזירה JSON, משהו כזה:
from flask import Flask, jsonify
app = Flask(__name__)
@app.route('/hello', methods=['GET'])
def hello():
message = "Hello, world!"
return jsonify(message=message)
if __name__ == '__main__':
app.run(debug=True)
ואתם מקנאים בילדים המקובלים מ JavaScript שיכולים פשוט לכתוב:
return {message}
ולא צריכים לחזור על השם פעמיים, פעם אחת בשביל המפתח ופעם שנייה בשביל הערך, אז אתם יכולים לכתוב:
@app.route('/hello', methods=['GET'])
def hello():
message = "Hello, world!"
return jsonify(**locals())
כדי לקבל בדיוק את אותה תוצאה, ובלי להקליד את שם המשתנה אפילו פעם אחת. או, אם אתם חוששים (בצדק) שאיזה משתנה שלא התכוונתם ייכנס לכם ל JSON תוכלו לכתוב:
@app.route('/hello', methods=['GET'])
def hello():
message = "Hello, world!"
return jsonify(**{k: v for k, v in locals().items() if k in ['message']})
וכן זה קצת ארוך אבל אפשר לקצר עם פונקציית עזר ואז נקבל:
def only(d, *keys):
return {k: d[k] for k in keys}
@app.route('/hello', methods=['GET'])
def hello():
message = "Hello, world!"
foo = 10
bar = 20
return jsonify(**only(locals(), 'message', 'bar'))
חמוד? בטח. אבל לא הייתי רוצה לראות את זה בקוד אמיתי. החיסכון לא מספיק חשוב בשביל לראות קוד לא סטנדרטי.1 417
next unless map.contains(node)
# Calculate Manhattan distances from the node to each antenna
dist1 = (node.x - p1.x).abs + (node.y - p1.y).abs
dist2 = (node.x - p2.x).abs + (node.y - p2.y).abs
# An antinode occurs when one antenna is exactly twice as far away as the other
if dist1 == 2 * dist2 || dist2 == 2 * dist1
# Store this antinode location (using a string key to ensure uniqueness)
key = "#{node.x},#{node.y}"
antinode_locations[key] = node
end
end
end
end
* The answer is the number of unique antinode locations *
puts "The number of unique locations containing an antinode is: #{antinode_locations.size}"
הטעות של קלוד בפיתרון השלישי מעניינת - תחילה הוא מפעיל את הפונקציה שלי כדי למצוא את ה Antinodes:
antinode1, antinode2 = p1.find_antinodes(p2)
אבל אז הוא מוודא שהתוצאות של הפונקציה באמת מקיימות את תנאי המרחק השווה:
dist1 = (node.x - p1.x).abs + (node.y - p1.y).abs
dist2 = (node.x - p2.x).abs + (node.y - p2.y).abs
* An antinode occurs when one antenna is exactly twice as far away as the other *
if dist1 == 2 * dist2 || dist2 == 2 * dist1
# Store this antinode location (using a string key to ensure uniqueness)
key = "#{node.x},#{node.y}"
antinode_locations[key] = node
end
כל הבלוק הזה כמובן מיותר כי מראש יצרנו את הנקודות כך שזה יהיה המרחק. גם הסריאליזציה של הקואורדינטות למחרוזת מיותרת כי ב Ruby אפשר לשמור Data Object בתור מפתח ב Hash.
מסקנות
אז מה למדנו מהניסוי? בינתיים לא הרבה. הנה כמה מסקנות שאני לוקח:
1. בעבודה עם AI חשוב לנסח בצורה מדויקת את הבעיה.
2. קלוד לא תמיד מאמין לתיעוד או למימוש שלנו. בינתיים לא הצלחתי לשכנע אותו לכתוב את הקוד בלי להוסיף את הבדיקה הכפולה.
3. מצד שני קלוד שמח להשתמש במימוש שלי ל Map ולא ניסה לכתוב בעצמו קוד שמפרסס את הקלט.
4. יהיה מעניין להבין למה קלוד שמח להשתמש בפונקציות שכתבתי ב Map אבל היה מאוד סקפטי לגבי הפונקציות שכתבתי ב Point, ואיך צריך לכתוב אבסטרקציות כדי ש AI ירצו להשתמש בהן.
5. אני חושב שבמערכת גדולה בעבודה עם Cursor כשאנחנו רוצים לוודא ש AI משתמש באבסטרקציות שאנחנו כותבים (למשל לוודא שהוא משתמש ב Custom Hook שכתבנו במקום לכתוב חדש) כדאי להוסיף לקונטקסט של ה Prompt גם דוגמת קוד שמשתמשת באותה אבסטרקציה. אולי זה אפילו יותר חשוב מהקוד המקורי של האבסטרקציה.
6. אנחנו עדיין לומדים. בעבודה עם AI מה שחשוב זה לעשות כמה שיותר ניסויים ולא לקחת "כללי אצבע", לא משנה מי כותב אותם. תראו מה עובד לכם ותמשיכו לבדוק כשיוצאים מודלים חדשים.1 417
המשכתי את הבדיקה עם פרומפט יותר ארוך. הפעם נתתי לו תיעוד API Docs של שתי המחלקות שכתבתי (קלוד עצמו יצר את התיעוד בשיחה אחרת איתו) ואז את הבעיה. אני מזכיר שבפיתרון שלי אחרי שכתבתי את הקוד לשתי המחלקות הפיתרון עצמו לקח בערך 5 שורות. כמו שחשדתי קלוד הסתבך בעיקר עם הבנת השאלה אבל תיעוד ה API כן עזר לו לחשוב והפעם הוא הדפיס קוד שהיה מרוצה ממנו אחרי 4 פעמים בלבד.
החיסרון? הקוד לא עבד והדפיס 0. זה מה שקלוד הדפיס:
Point = Data.define(:x, :y) do
def manhattan_distance(other)
[
(self.x - other.x).abs,
(self.y - other.y).abs
]
end
def find_antinodes(other)
# Calculate the differences between the two points
dx = other.x - self.x
dy = other.y - self.y
# Create antinode points
antinode1 = Point.new(self.x - dx, self.y - dy)
antinode2 = Point.new(other.x + dx, other.y + dy)
[antinode1, antinode2]
end
end
class Map
attr_reader :index_by_letter, :max_x, :max_y
def initialize(input_file_name)
@index_by_letter = Hash.new { |h, k| h[k] = [] }
@max_x = 0
@max_y = 0
File.readlines(input_file_name).each_with_index do |line, y|
line = line.chomp
@max_x = [line.length - 1, @max_x].max
@max_y = [y, @max_y].max
line.chars.each_with_index do |char, x|
unless char == '.'
@index_by_letter[char] << Point.new(x, y)
end
end
end
end
def contains(p)
p.x >= 0 && p.x <= @max_x && p.y >= 0 && p.y <= @max_y
end
end
def find_antinodes(map)
antinodes = Set.new
# Process each frequency (letter)
map.index_by_letter.each do |letter, positions|
# We need at least 2 antennas with the same frequency
next if positions.length < 2
# Check all pairs of antennas with the same frequency
positions.combination(2).each do |p1, p2|
# Calculate Manhattan distance components
diff_x, diff_y = p1.manhattan_distance(p2)
# Skip if the points aren't aligned (Manhattan distance check)
next unless diff_x == 0 || diff_y == 0 || diff_x == diff_y
# Calculate total Manhattan distance
total_distance = diff_x + diff_y
# Calculate distance between p1 and midpoint
mid_x = (p1.x + p2.x) / 2.0
mid_y = (p1.y + p2.y) / 2.0
dist_to_mid = (p1.x - mid_x).abs + (p1.y - mid_y).abs
# Check if one antenna is twice as far from the midpoint as the other
# This is equivalent to checking if the ratio of distances is 1:2
next unless (total_distance % 3 == 0)
# Calculate antinodes
antinode1, antinode2 = p1.find_antinodes(p2)
# Add antinodes if they're within map bounds
antinodes.add(antinode1) if map.contains(antinode1)
antinodes.add(antinode2) if map.contains(antinode2)
end
end
antinodes.size
end
map = Map.new("input.txt")
puts find_antinodes(map)
ניסיון 3 - קוד המחלקות עצמן
פה הבנתי ש API Docs זה אולי לא הדרך הכי טובה לעודד את קלוד לעבוד והלכתי לנסות כיוון אחר והדבקתי לקלוד את הקוד המלא של שתי המחלקות Map ו Point.
הפעם קלוד כתב ומחק 8 פיתרונות ורק בתשיעי הוא עצר והכריז שהפיתרון עובד נכון. הוא צדק. למרות האורך הפיתרון מציג את התשובה הנכונה:
* Solution for the Easter Bunny antenna problem *
* Load the map *
map = Map.new('input.txt')
* Track unique antinode locations using a hash for O(1) lookup *
antinode_locations = {}
* For each frequency *
map.index_by_letter.each do |freq, antennas|
# Skip if there's only one antenna with this frequency
next if antennas.length < 2
# Process each pair of antennas with the same frequency
antennas.combination(2) do |p1, p2|
# The find_antinodes method calculates potential antinode points
# based on the relative positions of p1 and p2
antinode1, antinode2 = p1.find_antinodes(p2)
# Check both potential antinodes
[antinode1, antinode2].each do |node|
# Skip if the node is outside the map boundaries1 417
ניסוי Advent Of Code יום 8 וקלוד
אחרי שפתרתי את החלק הראשון של יום 8 של Advent Of Code ולפני הסיבוך של החלק השני רציתי לנצל את הניסוחים המבלבלים של אריק ווסטל כדי לבעוט קצת בקלוד ונתתי לו לפתור את אותו תרגיל בשלוש גישות שונות.
האתגר
התרגיל בגדול לא מסובך אבל כן מנוסח בצורה מאוד מעייפת. יש לנו מטריצה עם אנטנות:
............
........0...
.....0......
.......0....
....0.......
......A.....
............
............
........A...
.........A..
............
............
נקודה מסמנת מקום ריק וכל תו אחר מסמן אנטנה. לכל אנטנה יש תדירות שזה התו שמופיע בה, כך שיש לנו בדוגמה 3 אנטנות בתדירות A ועוד 4 אנטנות בתדירות 0.
כל שתי אנטנות עם אותה תדירות יוצרות התנגשות שנקראת Antinode בשני הצדדים של הקו. הוא המחיש את הסיפור עם האיור הקטן הזה:
..........
...#......
..........
....a.....
..........
.....a....
..........
......#...
..........
..........
אז אנחנו רואים ששני ה a-ים הקטנים מתנגשים ויוצרים קו, ואז מאריכים את הקו קצת לכל צד ומקבלים שתי נקודות חדשות שהן ה Antinodes, מסומנות בסולמיות.
האתגר הוא למצוא את כל הנקודות במפה בהן יש Antinode, כשצריך לשים לב שאם יש 3 אנטנות באתה תדירות אז כל 2 מהן ייצרו Antinode.
הפיתרון שלי לתרגיל בשפת רובי הוא:
* frozen_string_literal: true *
Point = Data.define(:x, :y) do
def manhattan_distance(other)
[(x - other.x).abs, (y - other.y).abs]
end
def find_antinodes(other)
diff_x, diff_y = manhattan_distance(other)
if x < other.x
if y < other.y
# p1 is to the left and above p2
antinode1 = Point.new(x - diff_x, y - diff_y)
antinode2 = Point.new(other.x + diff_x, other.y + diff_y)
else
# p1 is to the left and below p2
antinode1 = Point.new(x - diff_x, y + diff_y)
antinode2 = Point.new(other.x + diff_x, other.y - diff_y)
end
else
if y < other.y
# p1 is to the right and above p2
antinode1 = Point.new(x + diff_x, y - diff_y)
antinode2 = Point.new(other.x - diff_x, other.y + diff_y)
else
# p1 is to the right and below p2
antinode1 = Point.new(x + diff_x, y + diff_y)
antinode2 = Point.new(other.x - diff_x, other.y - diff_y)
end
end
[antinode1, antinode2]
end
end
class Map
attr_accessor :matrix, :index_by_letter, :max_x, :may_y
def initialize(input_file_name)
@index_by_letter = Hash.new { |h, k| h[k] = [] }
@max_x = 0
@max_y = 0
File.read(input_file_name).lines.each_with_index.flat_map do |line, line_index|
line.chomp.split('').each_with_index.map do |char, column_index|
pos = Point.new(column_index, line_index)
@max_y = line_index if line_index > @max_y
@max_x = column_index if column_index > @max_x
@index_by_letter[char] << pos if char != '.'
end
end
end
def contains(p)
p.x <= @max_x && p.x >= 0 && p.y <= @max_y && p.y >= 0
end
end
m = Map.new('input.txt')
antinodes = m.index_by_letter.flat_map do |_char, positions|
positions.combination(2).flat_map { |p1, p2| p1.find_antinodes(p2) }
end
puts antinodes.filter { |p| m.contains(p) }.uniq.count
עכשיו לניסוי עם קלוד.
ניסיון ראשון - רק הבעיה
התחלתי את הניסוי עם הפרומפט הבא:
Solve the following general problem in Ruby (input is saved in file input.txt)
אחריו הדבקתי פשוט את כל עמוד התיאור של הבעיה מ Advent Of Code:
https://adventofcode.com/2024/day/8
עכשיו קלוד הוא מעניין כי הוא מדפיס קוד ואז בודק את עצמו ומרגיש שהוא לא עשה עבודה מספיק טובה ואז מדפיס עוד גירסה וככה בלולאה עד שהוא מרוצה. בפרומפט הזה לקח לו 6 ניסיונות ובסוף הוא הגיע לפיתרון עובד.
ניסוי שני - דחיפה קלה עם API Docs1 417
מזל טוב, קיבלת קידום!
כל מי שניהל פעילות Outsource מכיר את האתגר - להבין מה לתת למפתחים שבחוץ, איך לוודא את איכות התוצר, מה יהיה ממשק העבודה מולם, והכי חשוב, איזה החלטות הם יכולים לקבל לבד ואיזה החלטות חייב לקבל מישהו מבפנים.
אחרי יום עם GPT4.1 ב Cursor ברור לגמרי שזאת המציאות של כולנו.
ה AI יכול לכתוב כל קוד שניתן לו ברמה סבירה, אבל לאורך זמן אם תתנו לו לכתוב מספיק קוד הוא יהפוך את המערכת לבלתי ניתנת לתחזוקה, כי הוא רואה תמיד רק את המילה הבאה ולא את התמונה הגדולה.
עכשיו אתם - איזה החלטות אתם מתעקשים לקבל לבד? על מה אתם לא מוכנים להתפשר? ומה יישאר סימן ההיכר שלכם בקוד שה AI שלכם יכתוב?
1 417
למי אכפת ש null הוא אוביקט?
אמג'ד מסד, המייסד של רפליט, צייץ לאחרונה:
> I no longer think you should learn to code.
בהמשך לתגובות הוא מפרט ומרחיב את הרעיון, ומסביר שהוא עדיין חושב שיש מקום למהנדסי תוכנה, אבל קידוד יהיה מיותר. בציוץ המשך הוא הסביר שצריך לדעת איך דברים גדולים מתחברים יחד אבל אין צורך לדעת ש null הוא אוביקט ב JavaScript.
כמה מחשבות על זה-
1. מספרים ברנדון איי כתב את JavaScript בעשרה ימים ב 1995. אני מדמיין שהרבה מהרעיונות היו לו בראש לפני עשרת הימים האלה, אבל הזמן באמת פחות משנה. מה שכן משנה זה שעבור ברנדון איי של 1995 השאלה אם null צריך להיות אוביקט או לא היתה שאלה מהותית. היו שיקולים טובים לכאן ולכאן ובסופו של דבר זאת היתה החלטה שהתקבלה, יחד עם עוד מאות החלטות קטנות, כל אחת עם יתרונות וחסרונות משלה, שבנו יחד את האופי של השפה.
2. מהנדסי התוכנה שאמג'ד יצטרך הם בדיוק אנשים שאמורים לדעת לקבל החלטות קשות. הם אנשים יצירתיים, שמכירים את המערכת, את האילוצים שלה, את ההיבטים הטכניים במימוש כל מנגנון ואת היתרונות והחסרונות של כל בחירה. הם האנשים שצריכים לחבר בין הגדרת הדרישות לבין קידוד המערכת, וגם אם הם לא יכתבו בעצמם את הקוד אלא "רק" יגדירו ל AI איזה קוד לכתוב, הם עדיין יצטרכו להיות מאוד מקצועיים כדי שה AI יכתוב את הקוד הנכון עבור המערכת הספציפית שלהם.
3. אמג'ד, ובכירים רבים נוספים כן חושבים שהנדסת תוכנה היא דבר חשוב. הנה עוד ציוץ הפעם של Pratik Kotkar שקיבל הסכמה מאמג'ד באותו דיון:
> this doesn’t mean engineering is obsolete. The engineering approach to solving problems is now even more crucial to make best use of AI tools. the paradigm of what the focus of engineering just changes from syntax and semantics to problem solving
4. עכשיו לשאלות - איך לומדים Problem Solving בהנדסת תוכנה? איך מתאמנים על אותן המיומנויות שיהפכו אותנו למהנדסים הטובים של העתיד? האם לא סביר לחשוב שהשיקולים של ברנדון איי ב 1995 יכולים ללמד אותנו על פיתרון בעיות והנדסת תוכנה? אולי אפילו יותר מבניית עוד מערכת SaaS ?
5. ואולי אותם מהנדסים של העתיד, שיודעים לפתור בעיות ומבינים את המגבלות והאילוצים הטכניים וגם את ההזדמנויות של הטכנולוגיה, הם בעצם אותם אנשים שלמדו לעומק איך קוד עובד?
6. כשמישהו מציע להתמקד ב"הבנה" וב"פיתרון בעיות" ולדלג על הקידוד, אני מרגיש כאילו הוא מציע לי ללמוד דקדוק של שפה בלי ללמוד איך לדבר בה. אני לא בטוח שזה אפשרי.
נ.ב. קריאה מעניינת לגבי null ב JavaScript (תורגם מקוריאנית על ידי AI)
https://witch.work/en/posts/javascript-why-typeof-null-is-object
1 417
מקומפוננטה קטנה וקלה לתחזוקה קיבלתי מפלצת, והכי גרוע למרות שבטקסט הוא דיבר על field-sizing הוא לא השתמש בו בפיתרון ונשאר עם לוגיקת האפקט. עכשיו אני מכיר את התיאוריה שאם רק ניתן ל AI לכתוב את הקוד הנוראי שהוא רגיל לכתוב אז הוא יוכל לתחזק את הקוד מקצה לקצה ואנחנו נשב לשתות קפה בזריחה. עדיין לא ראיתי הוכחות לזה, אם כבר מה שכן ראיתי זה שכשאני נותן ל AI לעזור לי בקוד פשוט שגם אני יכול לכתוב הוא מצליח לזהות בעיות ולהציע תיקונים חכמים, אבל ככל שהקוד יותר ארוך ככה גם ל AI יותר קשה להישאר מרוכז. במילים אחרות קיבלתי ביצועים טובים יותר אבל במחיר של קוד הרבה יותר קשה לתחזוקה (גם לאנשים וגם לבוטים).
טייק 3 - שני פרומפטים
בניסיון שלישי אני רוצה לכוון את ה AI לנושא הביצועים ולתת לו להבין שיש כמה דרכים כדי לדחוף אותו לכיוון פיתרון ב CSS. אני מתחיל עם:
Code review the following component. Focus on performance. Only explain the problematic points without suggesting fixes.
Consider the larger picture. If there is a simpler way to achieve the same goal please point me to it, but don't implement yet any fixed version.
const ResizingTextArea = ({
value,
}) => {
const ref = useRef(null);
React.useEffect(() => {
if (!ref.current) return;
ref.current.style.height = "inherit";
ref.current.style.height = \${ref.current.scrollHeight}px\;
}, [value]);
return (
<textarea ref={ref} value={value}></textarea>
)
}
הפעם אני מקבל הסבר של הנקודות הבעייתיות שמסתיים בפיסקה
> There is a modern, potentially simpler, and more performant approach to achieve self-sizing textareas that relies primarily on CSS, minimizing or eliminating the need for JavaScript-based height calculations on every input change.
אני ממשיך את השיחה עם פרומפט נוסף וכותב:
Implement the most simple fix for the performance issue. Use inline style if you need to add CSS. Don't add new files
התוצאה הפעם:
const ResizingTextArea = ({
value,
// It's good practice to allow passing other props like onChange, placeholder etc.
...props
}) => {
// No more useRef or useEffect needed for resizing
return (
<textarea
value={value}
style={{
// This CSS property instructs the browser to size the field based on its content
fieldSizing: 'content',
// Optional: Prevents scrollbars from briefly appearing during resize in some browsers
// and ensures it doesn't overflow if a max-height is also applied elsewhere.
overflow: 'hidden',
// You might want to add a minHeight as well
// minHeight: '50px', // Example minimum height
}}
{...props} // Spread remaining props
/>
);
};
סוף סוף הוא קלע עם ה field-sizing. הוא הוסיף overflow שלא ביקשתי וגם את ה spread של כל הפרופס האחרים ששוב לא ביקשתי והכי גרוע אינסוף הערות שרק מקשות על ההבנה והתחזוקה, אבל אי אפשר לקבל הכל בחיים.
מה למדנו
הדוגמה חושפת את אותו צד של AI שאנחנו אוהבים להסתיר: הקונטקסט הוא הדבר החשוב. AI לא מבין מה חשוב ומה לא חשוב במערכת, הוא בסך הכל מגבר. הבחירה שלנו: האם להשתמש בו כדי להיות טובים יותר, כמו מורה פרטי שמסתכל על הקוד ויודע לכוון אותנו לשיטות עבודה טובות או פשוטות יותר וכך להפוך אותנו למפתחים יותר מיומנים; או כדי להיות טובים פחות, לכתוב במקומנו קוד שלא הוא ולא אנחנו נוכל לקרוא או לתחזק לאורך זמן.1 417
<textarea ref={ref} value={value}></textarea>
)
}
עכשיו הפלט הרבה יותר טוב. הוא שם לב לשימוש ב useEffect במקום ב useLayoutEffect, וגם שם לב שאפשר לבנות את אותו מנגנון עם CSS, אבל אז הוא הציע תיקון וקלקל את כל השיחה. זה הקוד המתוקן ש Gemini הציע:
import React, { useState, useRef, useLayoutEffect, useCallback } from 'react';
// Simple debounce function (or use one from lodash/underscore)
const debounce = (func, wait) => {
let timeout;
return function executedFunction(...args) {
const later = () => {
clearTimeout(timeout);
func(...args);
};
clearTimeout(timeout);
timeout = setTimeout(later, wait);
};
};
const ResizingTextArea = ({ value, onChange, style, ...props }) => {
const ref = useRef(null);
// Memoize the resize function using useCallback
const resize = useCallback(() => {
if (ref.current) {
// Reset height - necessary to shrink correctly
ref.current.style.height = 'inherit';
// Set height based on scrollHeight
ref.current.style.height = \${ref.current.scrollHeight}px\;
}
}, []); // No dependencies needed if it only accesses ref.current
// Debounce the resize function
// Adjust the delay (e.g., 150ms) as needed
const debouncedResize = useCallback(debounce(resize, 150), [resize]);
// Resize on value change (debounced)
useLayoutEffect(() => {
// Call debounced resize. It might run immediately the first time
// or after a delay on subsequent changes.
debouncedResize();
// We only want this effect to run when \value\ changes or debouncedResize changes.
}, [value, debouncedResize]);
// Resize on window resize (debounced)
useLayoutEffect(() => {
// Initial resize on mount
resize();
window.addEventListener('resize', debouncedResize);
// Cleanup listener on unmount
return () => {
window.removeEventListener('resize', debouncedResize);
// Optional: Cancel any pending debounced calls on unmount
// If using lodash debounce, it often has a .cancel() method.
// For the simple debounce above, clearing the timeout isn't
// easily exposed without modifying the debounce function itself.
// However, the check \if (ref.current)\ inside resize handles this.
};
}, [resize, debouncedResize]); // Rerun if resize functions change (shouldn't often)
// --- Enhancements for usability ---
// Allow parent to control the textarea fully
const handleChange = (event) => {
// Trigger the resize immediately on change for responsiveness *before* state update
// Note: This might cause a slight flicker if the debounced one runs later.
// Alternatively, rely solely on the debounced effect driven by \value\ prop change.
// For simplicity and relying on the debounced effect: comment the next line out.
// resize(); // Optional: Immediate resize attempt
if (onChange) {
onChange(event);
}
};
return (
<textarea
ref={ref}
value={value}
onChange={handleChange}
style={{
overflow: 'hidden', // Hide scrollbar that might briefly appear
resize: 'none', // Prevent manual resize corner if not desired
...style, // Allow passing custom styles
}}
rows={1} // Start with minimum rows (optional but common)
{...props} // Pass down other props like placeholder, name, etc.
/>
);
};
export default ResizingTextArea;1 417
בואו נשכנע את ג'מיני לשים לב לבעיה בקוד
ג'מיני 2.5 פרו הוא המודל הכי מקצועי היום (הכל יכול להשתנות מחר) ולמרות, ואולי עם כל היכולות שלו, בלי הנחיה נכונה הוא עלול לפספס דברים חשובים. בניסוי היום אני לוקח קטע קוד שמצאתי אתמול בגאמרוד ומנסה לשכנע את ג'מיני לשים לב לבעיית ביצועים ולהציע תיקון מחוץ לקופסה.
טייק 1 - Code Review
הקוד שמעניין אותי הוא הקומפוננטה CommentTextarea שמוגדרת בקובץ:
https://github.com/antiwork/gumroad/blob/main/app/javascript/components/Post/PostCommentsSection.tsx
יש שם שימוש לא אידאלי ב useEffect שפוגע בביצועים ומשנה את מאפיין הגובה כל פעם שהתוכן משתנה לפי גודל גובה התוכן. בדרך כלל עדכוני גובה כאלה צריכים להיות ב useLayoutEffect, ויותר מזה כשמדובר ב textarea יש מאפיין CSS בשם
field-sizing שגורם ל textarea לגדול אוטומטית לפי גודל התוכן. הייתי רוצה ש AI יזהה את הבעיה ויציע את התיקון הנכון בעזרת CSS, וגם ישלח אותי לקרוא על הקשר בין useEffect לביצועים ולמה לא כדאי להשתמש במבנה זה כדי שאלמד לפעם הבאה.
הניסיון הראשון שלי היה להדביק את כל הקוד לג'מיני ולבקש Code Review. זה היה הפרומפט:
const CommentTextarea = ({
children,
showAvatar = true,
...props
}: React.ComponentProps<"textarea"> & { showAvatar?: boolean }) => {
const appDomain = useAppDomain();
const { purchase_id } = useCommentsMetadata();
const loggedInUser = useLoggedInUser();
const ref = React.useRef<HTMLTextAreaElement | null>(null);
React.useEffect(() => {
if (!ref.current) return;
ref.current.style.height = "inherit";
ref.current.style.height = \${ref.current.scrollHeight}px\;
}, [props.value]);
return (
<div className={cx({ comment: showAvatar })} style={showAvatar ? {} : { display: "grid", gap: "var(--spacer-3)" }}>
{showAvatar ? (
<img className="user-avatar" alt="Current user avatar" src={loggedInUser?.avatarUrl ?? defaultUserAvatar} />
) : null}
{loggedInUser || purchase_id ? (
<textarea ref={ref} rows={1} placeholder="Write a comment" {...props} />
) : (
<div>
<a href={Routes.login_url({ host: appDomain })}>Log in</a> or{" "}
<a href={Routes.signup_url({ host: appDomain })}>Register</a> to join the conversation
</div>
)}
{loggedInUser != null || purchase_id != null ? (
<div style={{ display: "grid", justifyContent: "end", gap: "var(--spacer-3)", gridAutoFlow: "column" }}>
{children}
</div>
) : null}
</div>
);
};
הפידבק היה ארוך ומתחשב והסביר לי המון דברים לגבי הקוד. בתוך הטקסט האינסופי הוא התיחס ל useEffect באופן הבא:
Auto-Resizing useEffect:
Efficiency: The effect runs on every change to props.value (i.e., every keystroke). While often fine, for very complex components or extremely rapid typing, this could potentially lead to slight performance hiccups (though usually negligible). Debouncing is an option if performance becomes an issue, but adds complexity.
Initial Render: Ensure the initial height calculation works as expected when the component mounts and props.value might be empty or undefined. The rows={1} attribute helps set a minimum size initially.
CSS box-sizing: The accuracy of scrollHeight can be affected by CSS properties like padding and box-sizing. Ensure your CSS accounts for this (box-sizing: border-box is recommended).
ההצעה שלו, למרות שנראית הגיונית, היא לא הפיתרון הנכון וגם אין פה הבנה מדויקת של הבעיה.
טייק 2 - קומפוננטה קטנה ופוקוס על ביצועים
כתבתי גירסה מוקטנת של הקומפוננטה בפלייגראונד וניסיתי שוב, הפעם עם פוקוס על ביצועים:
code review the following component with focus on performance:
const ResizingTextArea = ({
value,
}) => {
const ref = useRef(null);
React.useEffect(() => {
if (!ref.current) return;
ref.current.style.height = "inherit";
ref.current.style.height = \${ref.current.scrollHeight}px\;
}, [value]);
return (
¡Ya disponible! Investigación de Telegram 2025 — los principales insights del año 
