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

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

להתאמן על החיבורים

05/06/2021

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

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

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

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

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

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

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

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

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

טיפ דוקר: משימות מתוזמנות

04/06/2021

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

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

עכשיו לתשובה הראשונה ומסתבר שגם היא לא מסובכת. בשביל להרים סרביס שפעם ביום יבצע איזושהי פעולה אני מגדיר ב Dockerfile שיריץ את הפקודה cron ובנוסף יוצר Cron Job כחלק מהאימג' שיריץ את המשימה המתוזמנת. כאן יש דוגמה שמצאתי למאגר עם כל הקבצים: https://github.com/cheyer/docker-cron

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

הקבצים המרכזיים משם הם הקובץ crontab שמגדיר את זמני הביצוע:

# Example of job definition:
# .---------------- minute (0 - 59)
# |  .------------- hour (0 - 23)
# |  |  .---------- day of month (1 - 31)
# |  |  |  .------- month (1 - 12) OR jan,feb,mar,apr ...
# |  |  |  |  .---- day of week (0 - 6) (Sunday=0 or 7) OR sun,mon,tue,wed,thu,fri,sat
# |  |  |  |  |
# *  *  *  *  * command to be executed

* * * * * /script.sh
# An empty line is required at the end of this file for a valid cron file.

הקובץ script.sh שמכיל את הסקריפט שצריך להריץ:

echo "$(date): executed script"

וכמובן הכי חשוב הוא ה Dockerfile:

FROM alpine:3.6

COPY crontab /etc/crontabs/root
COPY script.sh /

RUN chmod +x /script.sh

CMD ["crond", "-f", "-d", "8"]

בניה של האימג' והרצה תתן לכם את פלט המשימה המתוזמנת ל STDOUT של הקונטיינר כך שאפשר יהיה לראות את הפלט עם docker logs.

מתי לכתוב בדיקות

02/06/2021

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

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

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

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

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

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

איך להתחיל לשבור מונוליט למיקרו סרביסים

01/06/2021

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

אם אתם מעדיפים לעבור לפתח Micro Services בצעדים קטנים, הנה כמה הצעות לסרביסים שתוכלו להתחיל לפתח כבר היום בלי לשבור קוד קיים:

המשך קריאה

השאלות הנכונות

31/05/2021

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

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

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

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

כמה בעיות עם Feature Branches ומה אפשר לעשות במקום

30/05/2021

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

העיקרון הבעייתי מ Git Flow עליו אני רוצה להתמקד היום הוא השימוש ב Feature Branch. קודם כל אלה עיקרי המנגנון מתוך המדריך Gitflow Workflow:

  1. רוב העבודה מתבצעת על ענף בשם Develop.

  2. כל פיצ'ר חדש ייכתב ב Branch משלו.

  3. כשהפיצ'ר מוכן נשלב אותו חזרה ל Develop.

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

בעבודה על פרויקטים בגישה כזאת נתקלתי ב-4 הבעיות הבאות:

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

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

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

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

חלופה מעניינת ל Git Flow נקראת Continuous Integration. מארטין פולר כתב כהרגלו מאמר ארוך על הנושא עוד ב 2006 והוא רלוונטי גם היום. הרעיון בגדול הוא שכולם עובדים על אותו Branch ומשתמשים ב Feature Flags כדי שפיצ'רים מסוימים לא יוצגו ב UI.

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

חדש ב Node 16 - ממשק Promises ל Timers

29/05/2021

נוד 16 יצא לאחרונה ואיתו המון פיצ'רים חדשים ולא מאוד מעניינים (כאן יש רשימה גדולה).

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

import { setTimeout } from 'timers/promises';

async function doSomething() {
  console.log('doSomething started!');
  await setTimeout(2000);
  console.log('Delayed Timers!');
}

doSomething();

נכון, אנחנו יודעים לכתוב setTimeout שמחזיר Promise לבד, ובכל זאת נחמד לראות דברים כאלה נכנסים לשפה והופכים לסטנדרט. המעבר ל API מבוסס Promises מלווה אותנו תקופה ארוכה ב Node ובדפדפן.

באותו הקשר ומאותה רשימה, נוד 16 קיבל גם תמיכה ב Abort Controller שזה פיצ'ר שהרבה אנשים חיכו לו ומאפשר ביטול בקשת fetch. אפשר לקרוא עליו ב MDN יחד עם דוגמה לשימוש בפיצ'ר בדפדפן בקישור כאן: https://developer.mozilla.org/en-US/docs/Web/API/AbortController.

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

יש כבר אימג' בדוקר האב אז הפקודה הבאה מכל מכונה תזרוק אתכם לתוך REPL של נוד 16 כדי לנסות את השטויות החדשות:

$ docker run -it --rm node:16 node

ברגע שזה אפשרי

28/05/2021

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

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

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

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

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

  2. ולמי בכלל יש זמן לזה?

  3. ויש לי טסט לעוד שלושה חודשים חבל לא לנצל אותם.

  4. נו ובטח באותו יום שאקנה את האוטו מישהו יקנא וישרוט לי אותו.

  5. וממילא למכוניות יש ירידת ערך מטורפת עדיף לחכות עוד שנה.

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

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

ברגע שזה אפשרי זה הזמן הכי טוב להתחיל.

שאלות לראיונות עבודה: הפקודה throw ב Node.JS

27/05/2021

במדריך Hello World של RabbitMQ הם הציגו את הקוד הבא בתור דוגמה לקוד ששולח הודעה לתור הודעות:

var amqp = require('amqplib/callback_api');

amqp.connect('amqp://localhost', function(error0, connection) {
  if (error0) {
    throw error0;
  }
  connection.createChannel(function(error1, channel) {
    if (error1) {
      throw error1;
    }
    var queue = 'hello';
    var msg = 'Hello world';

    channel.assertQueue(queue, {
      durable: false
    });

    channel.sendToQueue(queue, Buffer.from(msg));
    console.log(" [x] Sent %s", msg);
  });
});

דני דפדפני רצה לשים את הקוד בתוך פונקציה ולהפעיל אותה בתוך try/catch כדי להציג הודעת שגיאה יפה יותר אם החיבור לתור נכשל, אז הוא עדכן את הקוד שיראה כך:

var amqp = require('amqplib/callback_api');

function doSend() {

    amqp.connect('amqp://localhost', function(error0, connection) {
    if (error0) {
        throw error0;
    }
    connection.createChannel(function(error1, channel) {
        if (error1) {
        throw error1;
        }
        var queue = 'hello';
        var msg = 'Hello world';

        channel.assertQueue(queue, {
        durable: false
        });

        channel.sendToQueue(queue, Buffer.from(msg));
        console.log(" [x] Sent %s", msg);
    });
    });
}

try {
    doSend();
} catch (err) {
    console.log('Sorry connection failed. Try again next year');
}

ולשאלות:

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

  2. למה היא לא עבדה?

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

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

אפשר לפרסם את התשובות שלכם בתגובות או אם אתם לא בטוחים שווה לקרוא את המדריך הזה כדי ללמוד איך לטפל בשגיאות ב Node.JS.