הבלוג של ינון פרק

טיפים קצרים וחדשות למתכנתים

דברים שאני עושה היום אחרת בזכות ה AI

05/12/2025

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

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

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

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

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

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

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

למי צלצלו הפעמונים

04/12/2025

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

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

  2. מי מתעדף את התיקון - צריך להגיע לפרודקט בשביל להכניס פיצ'ר "הקטנת JavaScript"? זה עוצר ספרינט? נתפס ב Code Review? ואם שמתי לב אחרי המיזוג?

  3. מה בכלל צריך לתקן - מתקנים כל מה שזורק Warning? רק אם ה Warning ממש מפחיד? רק אם יש זמן?

יש היום המון מנגנונים אוטומטיים שצועקים עלינו כשהקוד מזייף וככל שיש יותר מהם כך אנחנו מפתחים המון מנגנונים שמאפשרים להתעלם מאותם מנגנונים אוטומטיים. שמים Husky? תוך רגע אנחנו מגלים איך לבטל אותו. חייבים לעבור בדיקות בשביל לדחוף גרסה? נשים את הבדיקה ב skip רק לגרסה הזו. אנחנו מתעלמים מהאזהרות כי הרבה מהן הן False Positives. מתוך 3 הודעות שקיבלתי היום ב Code Review מ AI שלושתן היו טעויות של ה AI וכולן בזבזו לי זמן יקר לקרוא ולהבין למה ה AI התכוון ולמה הוא טועה. באותו זמן קובץ ה JavaScript במערכת כבר עבר את ה 5 מגה ואף אחד כבר לא זוכר מתי האזהרה על זה התחילה להופיע.

כמה רעיונות שיכולים לעזור:

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

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

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

המטרה שלנו פשוטה - לפתח יותר מהר ועם פחות טעויות. מערכות תוכנה שצועקות על כולם על כל שטות לא עוזרות.

קוד שנראה טוב אבל הוא בעצם לא (היוש AI)

03/12/2025

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

כאן יש PR לפיצ'ר שביקשתי מקופיילוט לכתוב על פרויקט קוד פתוח שלי שנקרא לנגלטס. ביקשתי שיבנה מסך נגן שיראה שיר מיוטיוב ולידו את המילים, התרגום, טבלה עם אוצר מילים מהשיר ואפשרות להוריד את אוצר המלים לקובץ CSV: https://github.com/ynonp/langlets-rails/pull/71

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

המשך קריאה

יותר מדי בדיקות?

02/12/2025

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

אבל מה קורה אם שינית משהו במערכת ו 10 בדיקות נשברו? מתקנים את כולן? או אם בכלל גילית שמשהו נשבר רק יומיים אחרי, כי הבדיקות יכולות לרוץ רק בענן ולוקח יומיים לקבל את התוצאות?

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

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

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

טיפ גיט: הצגת שינויים ב Merge Commit

01/12/2025

נתון הריפו הבא:

*   a883c36 (HEAD -> main) Merge branch 'develop'
|\
| * c363960 (develop) refactor to use function
* | d55f747 fixed text
|/
* 64b74f3 initial commit

יש לנו ענף main וענף develop, בענף develop ביצענו שינוי בקומי c363960 וענף main עשה שינוי באותו זמן ובאותו קובץ בקומי d55f747. בסוף מיזגנו את השינויים מ develop בקומיט a883c36.

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

$ git log -p -1

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

    Merge branch 'develop'

אני רואה את קומיט המיזוג אבל נראה שאין בו שינויים כלל! מה קורה פה?

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

--diff-merges=<format>
    Specify diff format to be used for merge commits. Default is `off`

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

 $ PAGER= git log -1 -p --diff-merges=1
commit a883c360dfae324a365d9bfb9ab881865a33f0c4 (HEAD -> main)
Merge: d55f747 c363960
Author: ynonp <ynonperek@gmail.com>
Date:   Sun Nov 30 13:56:36 2025 +0200

    Merge branch 'develop'

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

מזה אנחנו מבינים שענף develop הוציא את ההדפסה של Hello World לפונקציה נפרדת בשם greet. ומה עם ענף develop עצמו? אז אין ערך 2 ל diff-merges אבל ניתן להשתמש במילה separate כדי לראות את השינויים מול כל הענפים:

$ PAGER= git log -1 -p --diff-merges=separate
commit a883c360dfae324a365d9bfb9ab881865a33f0c4 (from d55f74779962b803a89f178a08065014dd74b3ac) (HEAD -> main)
Merge: d55f747 c363960
Author: ynonp <ynonperek@gmail.com>
Date:   Sun Nov 30 13:56:36 2025 +0200

    Merge branch 'develop'

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

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

    Merge branch 'develop'

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

 if __name__ == "__main__":
     greet()

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

אפשרות שלישית למתג זה היא combined או בקיצור c שמציגה את כל ההבדלים יחד:

$ PAGER= git log -1 -p --diff-merges=c
commit a883c360dfae324a365d9bfb9ab881865a33f0c4 (HEAD -> main)
Merge: d55f747 c363960
Author: ynonp <ynonperek@gmail.com>
Date:   Sun Nov 30 13:56:36 2025 +0200

    Merge branch 'develop'

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

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

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

$ PAGER= git log -1 -p --diff-merges=r
commit a883c360dfae324a365d9bfb9ab881865a33f0c4 (HEAD -> main)
Merge: d55f747 c363960
Author: ynonp <ynonperek@gmail.com>
Date:   Sun Nov 30 13:56:36 2025 +0200

    Merge branch 'develop'

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

 if __name__ == "__main__":
     greet()
->>>>>>> c363960 (refactor to use function)

מצב r מציג לדעתי את הפלט הכי ברור - אני רואה את שני הענפים, את השינויים של כל ענף ואת הודעות הקומיט המתאימות.

היום למדתי: פרטישן לפי זמן ומפתח ראשי

30/11/2025

לקחתי טבלת פוסטגרס בשביל הסיפור נניח שזו טבלת לקוחות:

CREATE TABLE customers (
    customer_id     INT PRIMARY KEY,
    full_name       TEXT NOT NULL,
    email_address   TEXT NOT NULL,
    created_at      TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
    updated_at      TIMESTAMP WITH TIME ZONE DEFAULT NOW()
);

ואז רציתי לחלק אותה ל Partitions כדי לשפר ביצועי שליפות (רוב השליפות צריכות למצוא לקוחות חדשים). אז הלכתי ל ChatGPT וביקשתי גרסה מחולקת של הטבלה. זה הקוד שהוא הדפיס:

CREATE TABLE customers (
    customer_id     INT PRIMARY KEY,
    full_name       TEXT NOT NULL,
    email_address   TEXT NOT NULL,
    created_at      TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW(),
    updated_at      TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW()
)
PARTITION BY RANGE (created_at);

הוא כמובן טעה.

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

הפתרון של ChatGPT לא מלהיב, הוא מציע להשתמש במפתח ראשי שמורכב משני שדות (ה id וה timestamp). אנחנו לא אוהבים מפתחות ראשיים מורכבים. אופציה שניה לא מלהיבה שהוא מציע היא לוותר לגמרי על המפתח הראשי, וגם זה לא מלהיב.

פתרון שנראה לי יותר טוב בתיאוריה (לא מימשתי עדיין) הוא להשתמש במפתח ראשי מסוג UUID שכולל timestamp למשל UUID7 או ULID. במפתח ראשי כזה אפשר לקחת את החלק של הזמן להיות מפתח החלוקה של הטבלה. כאן יש פוסט ארוך שמתאר את הרעיון:

https://elixirforum.com/t/partitioning-postgres-tables-by-timestamp-based-uuids/60916

בקצרה זה הקוד שלו ליצירת הטבלה:

create table(:payloads, primary_key: false, options: "PARTITION BY RANGE(id)") do
  add(:id, :binary_id, null: false, primary_key: true)

  # ... other fields and references
end

והקוד שיוצר את ה Partitions יהיה משהו כזה:

CREATE TABLE #{table}_p#{start_date.year}#{month}
PARTITION OF #{table} FOR VALUES
FROM (ulid_to_uuid('#{start_ulid}'))
TO (ulid_to_uuid('#{end_ulid}'))

התקנתי קרסר, מה עכשיו?

29/11/2025

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

המשך קריאה

שאלות ש AI לא יודע לענות עליהן

28/11/2025

למה שמת את כל הקוד בקובץ אחד?

למה בחרת את ה API הישן יותר?

למה כתבת מחדש את הפונקציה שכבר היתה כתובה במקום אחר (במקום להפוך אותה מ private ל public)?

למה שמרת את החיבור ל DB במשתנה גלובאלי ושכחת לאפס אותו וכך יצרת לי זליגת זכרון?

למה מחקת לי את הקובץ?

למה אתה מתעקש ליצור אוביקט בלולאה כשאני מתחנן שתכתוב Obect Literal?

למה לא יצרת אינדקסים על הטבלאות שיצרת ב DB?

למה יצרת קובץ locale חדש אפילו שכבר יש קובץ locale באפליקציה לכל הפיצ'רים האחרים?

למה בפונקציה אחת כתבת

setIsFlipped(!isFlipped);
if (!isFlipped) {
  setShowRating(true);
}

אבל שתי שורות למטה כתבת

const handleRate = (rating: number) => {
  onRate(rating);
  setIsFlipped(false);
  setShowRating(false);
};

(או שצריך if או שלא צריך)

למה בכפתור אחד כתבת stopPropagation אבל בכפתור השני לא?

למה אתה מושך מידע מהשרת עם useEffect ולא משתמש באיזה react-query ?

למה אתה כותב קוד שאפילו אתה לא תוכל לתחזק??


וואו מדהים ששמת לב! איזה חד אבחנה אתה. באמת הייתי צריך להשתמש במנגנון שאתה מציע. הנה מיד אני מתקן.


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

טיפ גיט: הצילו מחקתי את תיקוני הקונפליקט

27/11/2025

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

אני יוצר פרויקט עם שלושה קומיטים בשלושה ענפים, ענף main, ענף b וענף c:

* fb6d328 (HEAD -> c) c
| * fc29558 (b) b
|/
* c3482e6 (main) a

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

<<<<<<< HEAD
**The Clockmaker’s friend**
=======
**The Clockmaker’s bug**
>>>>>>> b

ענף c שנקרא HEAD מכיל את המחרוזת העליונה וענף b שאותו ניסיתי למזג מכיל את המחרוזת התחתונה ואני צריך לבחור. עד פה אין חדש. אנחנו גם יודעים לסדר את הקונפליקט, לעשות git add ואז קומיט שוב וליצור Merge Commit. אבל מה קורה אם אחרי ה add התבלבלתם והרצתם:

$ git merge --abort

פקודת merge abort מבטלת את המיזוג ומוחקת את כל השינויים המקומיים כך שהעולם חוזר להיות כמו לפני המיזוג. אבל רגע - מה קרה לתיקונים שלי? מה קרה לקונפליקט שפתרתי? הקובץ שהיה בקונפליקט עכשיו חזר להיות כמו בענף c לפני המיזוג. האם המאמץ שלי בפתרון הקונפליקט ירד לטמיון? האם אפשר לשחזר את הקובץ או שחייבים לפתור את הקונפליקט שוב?

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

git fsck --cache --no-reflogs --lost-found --unreachable HEAD

ומקבל רשימה של שטויות שהלכו לאיבוד:

unreachable tree 606ee30389b8548213f2f11596605040a54fe4e0
unreachable tree 72c93914a344c812fcf5fa7216cfd1e464bd9cc3
unreachable tree f85d50eb6572db3613432d0608b5338120883acb
unreachable blob 5c7cfc2892118a24ebbc3f8511010899e6d09781
unreachable blob dd0221c310613ea89104ce0fede8f10bf6df626a
unreachable blob 1be3a6878600782668ffaf33ff9b8843078a06cd
unreachable commit fc295586d6e98fea615ebec979850bd13b8441c3

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

$ git cat-file blob dd0221c310613ea89104ce0fede8f10bf6df626a
**The Clockmaker’s friend**
**The Clockmaker’s bug**

הבלוב dd0221c מכיל בדיוק את הקובץ אחרי ה add ולפני שזרקתי הכל עם merge --abort. כדי לקבל חזרה את הקובץ אני מריץ:

git cat-file blob dd0221c3106 > a.txt

בלי ידיים (שני קסמים)

26/11/2025

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

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

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

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

כמה לקחים לדברים אחרים שאנחנו לומדים שכדאי להשאיר בראש:

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

  2. יש קפיצות, אבל הן לא לפי הזמנה.

  3. תשומת לב היא המפתח להתקדמות, אבל בתחילת הדרך קשה למצוא זמן לשים לב וקשה לשים לב לדברים הנכונים.

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

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