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

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

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

20/08/2021

בתיאוריה, תרגום פרויקט מ Docker Compose ל Kubernetes הוא הדבר הכי קל בעולם: מפעילים סקריפט אחד שמתרגם את קבצי ה docker-compose.yml לקבצי ההגדרות של k8s, מעלים את התוצאה לקלאסטר קיים ונהנים מהחיים. המציאות לקחה לי קצת יותר עבודה ודרשה מספר שינויים בקוד המערכת - אבל בצד החיובי סיימתי עם קוד טוב יותר מזה שהיה בהתחלה וגם הבנה טובה יותר של הארכיטקטורה. מהתהליך גם למדתי לחבב מספר דברים בקוברנטס, והנה השלושה המרכזיים:

המשך קריאה

טיפים לשיחות מכירה טובות יותר

19/08/2021

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

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

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

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

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

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

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

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

איך לבדוק פעולות הקשורות לזמן עם Jest ו JavaScript

18/08/2021

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

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

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

import _ from 'lodash';

export default class MemoryGame {
  constructor(size) {
    this.cards = _.shuffle(_.range(size).flatMap(i => [i, i]).map(i => ({ value: i, visible: false })));
    this.foundPairIndexes = new Set();
    this.activeCardIndex = -1;
    this.hideTimer = null;
  }

  play = (index) => {
    const card = this.cards[index];
    if (card.visible) {
      return;
    }

    card.visible = true;
    clearTimeout(this.hideTimer);
    this.hideTimer = null;

    if (this.activeCardIndex >= 0) {
      if (this.cards[this.activeCardIndex].value === card.value) {
        // Found a match
        this.foundPairIndexes.add(this.activeCardIndex);
        this.foundPairIndexes.add(index);
        this.activeCard = null;
      } else {
        this.hideTimer = setTimeout(this.hideAllCards.bind(this), 2000);
      }
    }
    this.activeCardIndex = index;
  }

  isVisible(index) {
    if (this.foundPairIndexes.has(index)) {
      return true;
    }

    return this.cards[index].visible;
  }

  hideAllCards() {
    this.cards.forEach(c => { c.visible = false; });
  }
}

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

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

החלק שקשה לבדוק במשחק הוא השעון, ופה jest יכול מאוד לעזור לנו. הפונקציה jest.useFakeTimers() משתלטת על השעונים ומאפשרת לנו לזוז קדימה בזמן מהר יותר; הפונקציה jest.useRealTimers() מחזירה את המצב לקדמותו. אני אוהב להתחיל את בלוק הבדיקות שלי ב:

  afterEach(function() {
    jest.useRealTimers();
  });

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

אחרי שיש לנו Fake Timers אנחנו יכולים להשתמש בפונקציה advanceTimersByTime של jest כדי לרוץ קדימה בזמן. הנה הבדיקה שמוודאת ששתי שניות אחרי שהפכנו שני קלפים לא תואמים הם יתהפכו חזרה:

it('Turns back the cards when pair is not found', () => {
  jest.useFakeTimers();
  const g = new MemoryGame(5);
  const i1 = 0;
  const i2 = g.cards.findIndex(c => c.value !== g.cards[0].value);

  g.play(i1);
  g.play(i2);
  expect(g.isVisible(0)).toBeTruthy();
  expect(g.isVisible(i2)).toBeTruthy();

  jest.advanceTimersByTime(2500);

  expect(g.isVisible(0)).toBeFalsy();
  expect(g.isVisible(i2)).toBeFalsy();
});

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

התחלות

17/08/2021

המילה הראשונה שלמדתי ברוסית היתה mama; אחריה למדתי את motor ואז businessman. המשמעות של שלושתן זהה לאנגלית וכל מה שצריך בשביל ללמוד להגיד אותן הוא להוסיף את המבטא.

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

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

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

טיפ באש: השתמשו ב null glob כשצריך

16/08/2021

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

#!/bin/bash -e

for json_file in /data/*
do
  echo "Importing JSON file $json_file"
  python import_json.py "$json_file"
  echo "-- Done"
done

ולמה בלי לחשוב? כי אם הסקריפט import_json.py ינסה לפתוח את הקובץ שהוא מקבל לקריאה, והתיקיה ריקה, אנחנו נקבל את השגיאה:

FileNotFoundError: [Errno 2] No such file or directory: '/data/*'

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

# this will print all the files in /etc
$ echo /etc/*

# but this will just print "/data/*"
$ echo /data/*

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

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

$ shopt -s nullglob
$ echo /data/*

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

#!/bin/bash -e

shopt -s nullglob

for json_file in /data/*
do
  echo "Importing JSON file $json_file"
  python import_json.py "$json_file"
  echo "-- Done"
done

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

האבסורד של טכנולוגיות נגישות

15/08/2021

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

תקופה ארוכה PHP היה נגיש כי מספיק היה לשים קובץ PHP על השרת כדי לקבל אתר דינמי (בניגוד ל setup המסורבל של CGI Scripts שהיו לפניו). jQuery היה נגיש כי היה מאוד קל למתכנתים להבין איך להשתמש בו ולבנות קוד שעובד. ריילס ב 2005 היה מאוד נגיש כי הוא דיבר לאותם מאוכזבי PHP שחיפשו יותר סדר בקוד שלהם, אבל לא הרגישו בנוח לעבור ל Java או .NET, אנגולר1 היה יותר נגיש מ Backbone ו Ember למתכנתי Backend כי הוא איפשר להם "לדלג" על לימוד עולם ה Front End ולכתוב דברים סבירים בלי להבין את המכניקה שמתחת, והיום Vue יותר נגיש מ React או Angular מסיבות דומות.

האבסורד הוא שכמעט לכל טכנולוגיה נגישה מגיע בסוף רגע "ההתפכחות", שבו אנחנו מבינים שרק בגלל שהיה קל להתחיל לעבוד בטכנולוגיה מסוימת לא בהכרח הופך אותה לטובה. ב Angular1 זה היה עם הפופולריות של ג'ון פאפא וה Style Guide המפורסם שלו. ריילס הפכה למסורבלת באזור 2010 ככל שעלתה הפופולריות של Node.JS ומיקרו פריימוורקס בצד שרת ו jQuery הפסיקה להיות אטרקציה כשמתכנתים "רציניים" התחילו להביא לעולם ה Front End רעיונות של תכנות מונחה עצמים ואנקפסולציה שהם הכירו מסביבות אחרות, במיוחד החל מ 2008 עם הספר JavaScript The Good Parts.

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

מדריך Vue למתחילים - חלק 6 - יישומי Single Page Apps עם Vue Router

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

  1. פרק 1 - פיתוח קומפוננטה ראשונה

  2. פרק 2 - תקשורת בין קומפוננטות

  3. פרק 3 - תבניות דינאמיות

  4. פרק 4 - ממשק ההרכבה Composition API

  5. פרק 5 - דוגמת פיתוח נגן YouTube ב Vue

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

המשך קריאה

זה יעבוד בסוף

13/08/2021

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

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

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

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

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

איפור ואבטחת מידע

12/08/2021

הם לא הולכים ביחד, והבעיה שהאיפור רק מחמיר את המצב. נפלתי במקרה על הדף הזה היום: https://docs.npmjs.com/docker-and-private-modules

זה מתוך התיעוד של npm. הדף מסביר איך ליצור קובץ .npmrc בתור Docker Image, למשל בשביל למשוך אימג' ממאגר פרטי. הם ממליצים להשתמש ב Dockerfile הבא:

FROM risingstack/alpine:3.3-v4.3.1-3.0.1
ARG NPM_TOKEN
COPY .npmrc .npmrc
COPY package.json package.json
RUN npm install
RUN rm -f .npmrc
# Add your source files
COPY . .
CMD npm start

ובתבנית הבאה עבור npmrc:

//registry.npmjs.org/:_authToken=${NPM_TOKEN}

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

בשביל המשחק לקחתי את ה Dockerfile שלהם ובניתי ממנו אימג' וגם פירסמתי אותה ב Dockerhub. תראו אם תצליחו לגלות מה הערך של NPM_TOKEN בדוגמה שלי: https://hub.docker.com/layers/ynonp/dtest/latest/images/sha256-7eb4139557b15d6dd86cd5d9511863aaa03cb4271f3f799d701d5c80aa357575?context=explore&tab=layers.

איך אני מריץ שאילתת SQL מתוך הדפדפן?

11/08/2021

ש: איך אני מריץ שאילתת SQL מתוך הדפדפן?

ת: אתה לא

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

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