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

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

שתי גישות לתיקון באג

01/09/2018

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

  def expired?
    expires_at = DateTime.new(card_validity_year.to_i, card_validity_month.to_i, 1, 0, 0, 0) + 1.month
    Time.zone.today > expires_at
  end

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

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

  def expired?
    expires_at = Date.new(card_validity_year.to_i, card_validity_month.to_i, 1, 0, 0, 0) + 1.month
    Time.zone.today > expires_at
  end

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

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

require 'rails_helper'

describe PaymentToken do
  describe '#expired?' do
    it 'should return true when card is expired' do
      p = PaymentToken.new(card_validity_year: 1.year.ago, card_validity_month: 1)
      expect(p.expired?).to be(true)
    end
    it 'should return false when card is not expired' do
      p = PaymentToken.new(card_validity_year: 1.year.from_now, card_validity_month: 1)
      expect(p.expired?).to be(false)
    end
  end
end

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

טעינת קובץ CSS בצורה אסינכרונית

31/08/2018

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

<!DOCTYPE html>
<html>
  <head>
    <title>Hello CSS</title>
    <link rel='stylesheet' href='startup.css' />
    <link rel='stylesheet' href='all.css' />
  </head>

  <body>
    <h1>Hello CSS</h1>
    <div class='fold'>
      <p>consider this text which will only appear after the larger stylesheet file is loaded</p>
    </div>
  </body>
</html>
body {
  background: orange;
}

.fold {
  display: none;
}
.fold {
  display: block;
}

p {
  font-size: 1.5em;
  line-height: 2;
}

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

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

<!DOCTYPE html>
<html>
  <head>
    <title>Hello CSS</title>
    <link rel='stylesheet' href='startup.css' />

      <script>
        var link = document.createElement('link');
        link.rel='stylesheet';
        link.href = 'all.css';
        document.head.appendChild(link);
      </script>
      <noscript>
        <link rel='stylesheet' href='all.css' />
      </noscript>
  </head>

  <body>
    <h1>Hello CSS</h1>
    <div class='fold'>
      <p>consider this text which will only appear after the larger stylesheet file is loaded</p>
    </div>
  </body>
</html>

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

<link rel="preload" href="mystyles.css" as="style" onload="this.rel='stylesheet'">

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

היכרות עם Stimulus.js

30/08/2018

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

המשך קריאה

עריכה

29/08/2018

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

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

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

מה תעשו כשהקוד שלכם יתיישן?

28/08/2018

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

מה תעשו כשזה יקרה?

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

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

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

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

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

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

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

ככה לא כותבים בדיקות תקינות

27/08/2018

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

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

$ hebcal 4 2059
4/1/2059 Pesach IV (CH''M)
4/2/2059 Pesach V (CH''M)
4/3/2059 Pesach VI (CH''M)
4/4/2059 Pesach VII
4/5/2059 Pesach VIII
4/10/2059 Yom HaShoah
4/13/2059 Rosh Chodesh Iyyar
4/14/2059 Rosh Chodesh Iyyar
4/16/2059 Yom HaZikaron
4/17/2059 Yom HaAtzma'ut
4/27/2059 Pesach Sheni

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

$ hebcal 99999 2059
Segmentation fault: 11

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

https://github.com/hebcal/hebcal

הפונקציה הראשונה שצריכה להטריד אותנו היא זו שמופעלת כדי לפענח את הפרמטרים (הקובץ start.c) ושם אנחנו פוגשים את הקטע הבא:

startAbs = greg2abs(tempDate);
tempDate.dd = MonthLengths[LEAP(theYear)][theMonth];
endAbs = greg2abs(tempDate);

שכולל גישה ישירה למערך בשם MonthLengths לפי הערך של המשתנה theMonth. ומאיפה משתנה זה הגיע? נגלגל קצת למעלה כדי למצוא את:

 theMonth = atoi(remain[0]);

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

       if (theMonth > 12)
               die("The month must be less than 13", "");

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

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

tempDate.dd = MonthLengths[LEAP(theYear)][theMonth];

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

int getMonthLength(int year, int month)
{
    if (month < 0 || month > 13)
    {
        return 0;
    }
    return MonthLengths[LEAP (year)][month];
}

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

אפשר לראות את השינויים המלאים שהצעתי לפרויקט ב Pull Request שהעליתי כאן:

https://github.com/hebcal/hebcal/pull/157/files

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

רעיונות לא טובים

26/08/2018

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

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

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

אל תשכחו למלא מים

25/08/2018

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

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

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

נ.ב. הפוסט הזה לא באמת היה על מגהצים. טוב אולי קצת.

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

רוצים לזכות במנוי לשנה במתנה לאתר?

24/08/2018

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

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

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

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

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

מחכה לשמוע מכם ינון

פתוח-סגור

23/08/2018

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

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

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

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

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

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