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

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

מה ההבדל בין git rm cached ל git reset

30/12/2019

הגיעה אליי שאלה מעניינת הבוקר במייל וחשבתי לשתף את התשובה כאן לטובת כולם. והשאלה: מה ההבדל בין הפקודה git rm --cached לבין הפקודה git reset, מתי אשתמש בכל אחת?

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

הפקודה git add לוקחת קובץ מתיקיית העבודה ומעתיקה אותו לאותה תיקיה זמנית של "הכנה לקומיט" שקראתי לה Staging Area. הפקודה git reset לוקחת קובץ מהריפוזיטורי ומעתיקה אותו לאותה תיקיה זמנית. לכן אפשר לחשוב על שתי פקודות אלה בתור הפכים. נכין רגע מאגר לדוגמא:

$ mkdir demo
$ cd demo
$ git init
$ echo one > demo.txt
$ git add demo.txt
$ git commit -m 'commit 1'

אז כשאני יוצר קובץ חדש בתיקיית העבודה אני יכול להשתמש ב git add כדי להוסיף אותו לתיקיה הזמנית:

$ echo two > two.txt
$ git add two.txt 
$ git ls-files --stage
100644 5626abf0f72e58d7a153368ba57db4c673c0e171 0   demo.txt
100644 f719efd430d52bcfc8566a43b2eb655688d38871 0   two.txt

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

$ git reset two.txt
$ git ls-files --stage
100644 5626abf0f72e58d7a153368ba57db4c673c0e171 0   demo.txt

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

לעומתה הפקודה git rm הרבה יותר דומה לפקודה git add, רק שבעוד ש git add מעתיקה קובץ מתיקיית העבודה ל Staging Area, תפקיד הפקודה git rm הוא להעתיק את ה"אין קובץ", כלומר למחוק קובץ מה Staging Area.

נזכור שהדרך הרגילה שלנו להוציא קובץ מה Staging Area היא לדרוס את הקובץ ולהחליף אותו בגירסא ששמורה בריפוזיטורי, וזה מה שעושה git reset. אבל אם אנחנו רוצים למחוק קובץ לחלוטין זה לא באמת יעזור להעתיק את הגירסא מהריפוזיטורי. במילים אחרות בדוגמא שלנו הקובץ demo.txt נמצא ב Staging Area וגם ב Repository. אם אפעיל git reset עליו לא יקרה כלום:

$ git ls-files --stage
100644 5626abf0f72e58d7a153368ba57db4c673c0e171 0   demo.txt

$ git reset demo.txt
$ git ls-files --stage
100644 5626abf0f72e58d7a153368ba57db4c673c0e171 0   demo.txt

בכל מקרה הוא נשאר ב Staging Area וייכנס לקומיט הבא. הפקודה git rm היא הדרך שלנו להעתיק את העובדה שהקובץ לא קיים אל ה Staging Area. שימו לב להשפעה שלה על המאגר שלנו:

$ git rm demo.txt
$ git ls-files --stage
$ ls
two.txt

עכשיו ה Staging Area ריק. מאחר והקומיט הבא שיווצר הוא בסך הכל עותק של ה Staging Area ברגע יצירת הקומיט, הקובץ demo.txt לא יהיה חלק מהקומיט הבא. הפעם לא לקחנו את הגירסא מהריפוזיטורי והעתקנו אותה ל Staging Area, אלא בדיוק כמו עם add לקחנו את הגירסא מתיקיית העבודה והעתקנו אותה ל Staging Area, אבל עשינו את זה אחרי שמחקנו את הקובץ מתיקיית העבודה.

ומה לגבי git rm --cached ? אם הגעתם עד לפה הסיפור שלו הוא הכי פשוט מכולם. לפעמים אנחנו תקועים עם קובץ או תיקייה שאנחנו רוצים להוציא מהריפוזיטורי אבל להשאיר אצלנו בתיקיית העבודה. דוגמא קלאסית היא תיקיית node_modules: בטעות הוספנו תיקיה זו למאגר ועכשיו אנחנו רוצים להוציא אותה, אבל התיקיה כן עוזרת לי בעבודה השוטפת ולכן אני לא רוצה למחוק אותה מהדיסק. הפעלת git rm -r node_modules לא באה בחשבון כיוון שהיא תביא למחיקת התיקיה מהדיסק. אבל, אם נוסיף את --cached נקבל גירסא של git rm שלא מוחקת את הקובץ או התיקיה מהדיסק אלא רק מה Staging Area.

בחזרה למאגר הדוגמא נחזיר את הקובץ demo.txt מהריפוזיטורי עם:

$ git restore -s HEAD demo.txt
$ git ls-files --stage
100644 5626abf0f72e58d7a153368ba57db4c673c0e171 0   demo.txt

ועכשיו נמחק אותו רק מה Staging Area בלי למחוק אותו מתיקיית העבודה:

$ git rm --cached demo.txt
$ ls
demo.txt two.txt
$ git ls-files --stage

למה (ואיך) להמשיך ללמוד טכנולוגיה לאורך שנים

29/12/2019

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

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

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

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

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

אבל- איך עושים את זה? מהי אותה קומה שניה? ואיך אני יודע שאני לא עושה פשוט "עוד מאותו דבר?". הנה שלושה טיפים שעוזרים לי להתמיד:

המשך קריאה

מנגנוני ניהול זיכרון ב Python

28/12/2019

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

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

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

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

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

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

a = []
a.append(a)

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

>>> len(a)
1
>>> len(a[0])
1
>>> len(a[0][0])
1
>>> len(a[0][0][0])
1
>>> len(a[0][0][0][0])
1
>>> len(a[0][0][0][0][0])
1

אבל החיסרון הוא שמנגנון Reference Count כבר לא יכול לעבוד. נלך לראות מה פייתון חושב על הקוד שלנו.

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

>>> a = []
>>> sys.getrefcount(a)
2

>>> b = {}
>>> sys.getrefcount(b)
2

>>> a = 10
>>> sys.getrefcount(a)
12

שני הראשונים די מסבירים את עצמם: השם a הוא השם הראשון של הרשימה, ואז בתוך הפונקציה getrefcount יש לרשימה זו שם נוסף (זה שם הפרמטר שהופיע בסוגריים בהגדרת הפונקציה).

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

>>> a = 19821
>>> sys.getrefcount(a)
2

עכשיו אפשר לחזור למעגל שלנו:

>>> a = []
>>> a.append(a)
>>> sys.getrefcount(a)
3

לאוביקט יש הפעם 3 שמות: השניים הראשונים מוכרים לנו מהדוגמאות הקודמות והשם השלישי הוא ההצבעה הפנימית. מנגנון Reference Count לעולם לא יוכל למחוק משתנה זה מהזיכרון כי מספר השמות שלו לא ירד אף פעם ל-0. ההצבעה הפנימית תימחק רק כשנאפס אותה ידנית.

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

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

import sys, gc
a = []
a.append(a)
ida = id(a)
a = 10

# is there still an object with id == i ?
>>> ida in [id(x) for x in gc.get_objects()]
True

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

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

>>> gc.collect()
1

>>> ida in [id(x) for x in gc.get_objects()]
False

ועכשיו הרשימה עם המעגל נמחקה מהזיכרון.

בזכות השילוב בין שני המנגנונים הרבה יותר קשה לייצר זליגות זיכרון בתוכניות פייתון. כדאי לשים לב עם זאת שמנגנון ה Garbage Collector מתעורר בזמנים לא צפויים ועלול לפגוע בביצועים של התוכנית אם אנחנו רוצים לכתוב תוכנית שתגיב בזמן אמת לאירועים חיצוניים. לכן קיימת בפייתון הפקודה gc.disable שמבטלת את מנגנון ה Garbage Collection ומשאירה אותנו רק עם Reference Count. אם אתם בטוחים שאין לכם הצבעות מעגליות באפליקציה וממש צריכים ביצועי Real Time אולי זה משהו ששווה לבדוק אותו.

לא רוצה, לא יכול ולא יודע איך

27/12/2019

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

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

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

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

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

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

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

שגיאות זה לא אוכל

26/12/2019

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

  def self.track_event(user_token)
    return if user_token.nil?

    session = UserSession.active.find_by(token: user_token)
    session&.update_attribute('last_seen', Time.now)
  end

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

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

אגב האופרטור &. אומר שאם האוביקט אינו nil אז צריך להפעיל את הפונקציה, אבל אם הוא כן nil אז לא עושים כלום. האופרטור נקרא Optional chaining וגירסא שלו תגיע בקרוב גם ל JavaScript.

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

קוד סטארטר לפיתוח Dashboard ב React

25/12/2019

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

המשך קריאה

ללמוד איך משתמשים או איך זה בנוי

24/12/2019

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

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

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

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

שדרוגים

23/12/2019

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

בכל מוצר תוכנה שאני יכול לחשוב עליו שידרוג בין גירסאות קרובות הוא הרבה יותר קל מאשר שידרוג בין גירסאות רחוקות. המעבר מריילס 5.1 ל 5.2 הולך להיות הרבה יותר פשוט מאשר קפיצה מ-4 ל-6. אותו דבר עם אנגולר, ריאקט, Java או כל טכנולוגיה אחרת.

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

חדש באתר: קורס ריאקט

22/12/2019

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

אבל לפני הכל ולממהרים - זה הלינק לקורס: https://www.tocode.co.il/bundles/react

המשך קריאה

ביי ביי Redux Thunk

21/12/2019

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

המשך קריאה