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

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

ניטרול מנגנוני הגנה

17/07/2019

בהודעה מפורסמת ב Stack Overflow מ 2011 אחד הגולשים שאל איך לשתף Session Cookie בין ASP.NET ל Java Applet. התשובה עם ה-וי שם המליצה לו לנטרל את מנגנון ההגנה שנקרא HTTP Only Cookie. זאת ההתכתבות:

https://stackoverflow.com/questions/201699/sharing-asp-net-session-cookies-with-a-java-applet/656465

העצה עובדת אבל המחיר אני חושב גדול מהתועלת. ניטרול מנגנוני הגנה בלי לבנות מנגנון אחר במקומם הוא רעיון רע: בסיטואציה כמו שתוארה מנגנון ההגנה שנקרא HTTP Only Cookie אמור לשמור על הגולש ממתקפת XSS שתגנוב לו את ה Session Token.

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

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

בורח להרים

16/07/2019

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

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

  2. אה רגע זה מעניין, למה הם כתבו כאן את הפונקציה הזאת ולא את ההיא?

  3. מה הולך פה?! איך לא ראיתי את כל ערימת התיעוד הזאת עד עכשיו? אין מצב שאצליח ללמוד את כל זה בחודשיים (או אפילו בשנתיים)... חייב לברוח להרים.

  4. אה ככה זה עובד. עכשיו אני מבין. בעצם זה לא כזה שונה מהדבר ההוא שאני כבר מכיר.

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

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

והדבר המדהים כאן הוא שהפחד והרצון לברוח להרים לא נעלם, גם אחרי אלף מצגות. באנגלית יש משפט "The only way out is through". אז נסו גם אתם לרוץ בתוך ולא לברוח להרים. ואם אתם ממש חייבים לברוח אז לפחות קחו משהו לשמוע בדרך:

ומה אם?

15/07/2019

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

def fib(n):
    if n == 0 or n == 1: return 1

    return fib(n-1) + fib(n-2)

ועכשיו נשאל -

  1. ומה אם n הוא שלילי?
  2. ומה אם n הוא בכלל לא מספר?
  3. ומה אם n הוא מספר גדול מאוד?
  4. ומה אם אני צריך לחשב סכום של 10 מספרי פיבונאצ'י?
  5. ומה אם אני צריך לשפר ביצועים ולהעביר חלק מהחישובים לתהליכון נפרד?
  6. ומה אם הקוד שלי צריך לרוץ על מכונה שאין בה הרבה זיכרון?

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

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

למה הכל מסובך כאן?

14/07/2019

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

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

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

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

אצלי אין באגים - חלק 2

13/07/2019

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

כתזכורת זאת הפונקציה ברובי איתה היתה בעיה:

  def perform(workshop_instance)
    return if (workshop_instance.date - Time.zone.now) > 1.hour
    return if workshop_instance.passed?

    zoom = Zoom.new
    emails = zoom.meeting_registrants(workshop_instance.zoom_id)
    NotificationsMailer.webinar_reminder(workshop_instance, emails).deliver_now
  end

שני סוגי הבדיקות המרכזיים שנרצה לכתוב הם Acceptance tests ו Unit tests. בדיקות מסוג Acceptance Tests יאפשרו לנו להיות רגועים ולדעת ששעה לפני הוובינר כשהג'וב הזה ירוץ הוא באמת ישלח את המייל. בדיקות מסוג Unit tests יעזרו לנו למצוא באגים בקוד אם אנחנו חושדים שתרחישים מסוימים הולכים להתפוצץ.

כדי להחליט איזה Acceptance Tests אנחנו צריכים נסתכל על הקוד כחלק מתהליך עסקי ונרצה לשאול: מה המטרה של הקוד הזה? מה הקלט שהוא מקבל? באיזה מצבים הוא אמור לרוץ? ומה אמור לקרות בכל אחד מהמצבים? הנה הרשימה שלי:

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

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

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

הבחירה כמה Acceptance Tests לכתוב וכמה להתאמץ כדי לדמות את המציאות בצורה כמה שיותר מדויקת היא המשחק שלכם בכתיבת הטסט. יש מצבים שאנחנו רוצים בדיקות כמה שיותר ריאליסטיות וכמה שיותר מדויקות, ויש מצבים שאנחנו מוכנים להסתפק בפחות בשביל שיהיה יותר קל לכתוב את הטסט או יותר מהר להריץ אותו. בריילס למשל יש כבר מנגנון של Mock לתיבות אימייל ולכן כמעט אף פעם אני לא מגדיר תיבת אימייל אמיתית ובודק שמייל באמת נשלח אליה. מצד שני בעבודה עם APIs חיצוניים (כמו זום במקרה של הקוד הזה) כמעט תמיד אעדיף להשתמש ב API האמיתי בתוך הטסט כדי לוודא שאני תופס שינויי API מצד הספק.

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

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

שורה ראשונה:

    return if (workshop_instance.date - Time.zone.now) > 1.hour

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

    return if workshop_instance.time_to_start > 1.hour

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

שורה שניה:

    return if workshop_instance.passed?

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

שורה שלישית:

    zoom = Zoom.new

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

שורה רביעית:

    emails = zoom.meeting_registrants(workshop_instance.zoom_id)

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

בשביל הבדיקה השניה נתבונן בשורה הבאה:

    NotificationsMailer.webinar_reminder(workshop_instance, emails).deliver_now

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

סך הכל בדיקות היחידה שנרצה לכתוב הן:

  1. לוודא שבדקו אם WorkshopInstance.time_to_start גדול משעה.

  2. לוודא שבדקו אם WorkshopInstance.passed?.

  3. לוודא שהפעילו את Zoom.meeting_registrants עם מזהה הפגישה הנכון.

  4. ליצור Zoom.meeting_registrants פיקטיבי שמחזיר רשימה של פרטי נרשמים מזויפים כמו שזום היה עשוי להחזיר, ולוודא שהפונקציה NotificationsMailer.webinar_reminder מקבלת את רשימת האימיילים של הנרשמים.

אם היינו כותבים את כל הבדיקות האלה היינו מגלים את הדברים הבאים:

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

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

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

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

אצלי אין באגים

12/07/2019

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

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

  def perform(workshop_instance)
    return if (workshop_instance.date - Time.zone.now) > 1.hour
    return if workshop_instance.passed?

    zoom = Zoom.new
    emails = zoom.meeting_registrants(workshop_instance.zoom_id)
    NotificationsMailer.webinar_reminder(workshop_instance, emails).deliver_now
  end

חשבתי - יש פה 5 שורות... מה כבר יכול להשתבש?

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

require "rails_helper"

RSpec.describe WebinarReminderJob, :type => :job do
  let (:wi) { workshop_instances(:one) }

  before(:each) do
    wi.create_zoom_meetup
  end

  after(:each) do
    wi.delete_zoom_meetup
  end

  describe "#perform" do
    it "sends an email to workshop participants" do
      zoom = Zoom.new
      zoom.add_user(wi.zoom_id, email: 'testuser@gmail.com', first_name: 'test', last_name: 'user')

      WebinarReminderJob.perform_now(wi)

      expect(ActionMailer::Base.deliveries.last.to).to eq(['testuser@gmail.com'])
    end
  end
end

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

כמה נקודות לגבי הבדיקה ששווה לשים לב אליהן, גם אם לא מכירים רובי:

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

  2. יש Mock למנגנון המייל - המייל לא באמת נשלח למשתמש הבדיקה. באופן אוטומטי בגלל שרצים בטסט ריילס שומר את המייל שהיה אמור לשלוח בתוך אוביקט בשם ActionMailer::Base.deliveries. המוק הזה נוצר אוטומטית כחלק מהפריימוורק לכן אני שמח להשתמש בו.

  3. אני לא בודק כאן שהמייל נשלח כמו שצריך או נראה טוב. אלה בדיקות אחרות שצריך לכתוב אותן ביום אחר (וממילא הן בודקות מנגנונים אחרים ולא את ה-5 שורות שראינו למעלה).

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

פשוט כי בא לי להחליף

11/07/2019

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

כמעט בכל המקרים ההתלבטות בין טכנולוגיות לא קשורה לדברים שאפשר או אי אפשר לעשות עם הטכנולוגיה, אלא למסר שאתם רוצים לשדר לעובדים שלכם ולעצמכם. הבחירה להחליף מ React ל Vue או מ Node.JS ל Elixir (או ההיפך) לא באה בגלל שלא הצלחתם לבנות משהו ב React, או ש Node לא נתן ביצועים מספיק טובים.

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

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

זה לא בשבילך

10/07/2019

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

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

  1. לבטל את טופס צור קשר. מי שבאמת רוצה ימצא דרך להגיע.

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

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

  4. לפרסם באתר את המחיר, ושיהיה כפול מכל מחיר אחר שאתם מוצאים ברשת.

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

איך לתקן כל באג בעולם

09/07/2019

מסתבר שלתקן באגים זה דבר ממש קל. התהליך מורכב בסך הכל מ-3 שלבים:

  1. מתחילים בכתיבת טסט שנכשל בגלל הבאג.

  2. מתקנים את הבאג.

  3. רואים שהטסט עובר ועושים קומיט.

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

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

08/07/2019

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

  1. האקר זדוני מקלקל שורה בקובץ ועושה קומיט.

  2. חבר טוב בטעות מוחק את הקובץ כי משהו שם נראה חשוד.

  3. החבר מיד מבין שטעה והקובץ דווקא היה חשוב, אז מפעיל revert כדי להחזיר את הקובץ.

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

הנה הכל בהמחשה על מאגר לדוגמא. המאגר כרגע כולל רק קובץ אחד בשם demo.txt:

$ git log --oneline
193c20a (HEAD -> master) initial commit

$ ls
demo.txt

$ cat demo.txt
hello
this is a demo file
created by mr. nice guy

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

# now evil hacker arrives
$ sed -i .bak 's/mr. nice guy/evil hacker/' demo.txt
$ rm demo.txt.bak
$ git commit -a -m 'hahaha'

$ git log --oneline
c4a9b1b (HEAD -> master) hahaha
193c20a initial commit

ואחרי זמן מה חבר נחמד בטעות מוחק את הקובץ ומחזיר אותו עם revert:

$ git rm demo.txt
$ git commit -m 'oops'
$ git revert HEAD
$ git log --oneline

5bc7d72 (HEAD -> master) Revert "oops"
0f96ad4 oops
c4a9b1b hahaha
193c20a initial commit

כשננסה להפעיל git blame על הקובץ נגלה להפתעתנו שמי שכתב את השורה האחרונה הוא החבר שלנו בקומיט האחרון:

$ git blame demo.txt
5bc7d722 (ynonp 2019-07-07 15:24:36 +0300 1) hello
5bc7d722 (ynonp 2019-07-07 15:24:36 +0300 2) this is a demo file
5bc7d722 (ynonp 2019-07-07 15:24:36 +0300 3) created by evil hacker

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

אני רוצה לדלג על 5bc7d722 אז אעביר את זה שלפניו:

$ git blame 5bc7d72~ demo.txt
fatal: no such path demo.txt in 5bc7d72~

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

$ git blame 5bc7d72~~ demo.txt
^193c20a (ynonp 2019-07-07 15:20:43 +0300 1) hello
^193c20a (ynonp 2019-07-07 15:20:43 +0300 2) this is a demo file
c4a9b1b5 (ynonp 2019-07-07 15:23:06 +0300 3) created by evil hacker

וכך מגלים שהקומיט הבעייתי הוא c4a9b1b5.