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

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

שימו לב: נוד קורא גם את ה gitignore שלכם כשמפרסמים חבילות

17/03/2022

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

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

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

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

ניסיון שני בבניה, שוב publish, שוב install ו... dist עדיין לא שם.

בסופו של דבר ברחתי מהילדים למקום שקט, הסתכלתי בשקט בספריה ומצאתי שם קובץ .gitignore עם השורה:

dist/

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

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

טיפ טייפסקריפט: הפקודות Parameters ו ReturnType

16/03/2022

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

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

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

function setTextAndCount(stuff) {
    setText(stuff);
    setCount(c => c + 1);
}

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

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

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

function setTextAndCount(
  ...args: Parameters<typeof setText>
): ReturnType<typeof setText> {
  setText(...args);
  setCount((c) => c + 1);
}

דוגמת קוד מלאה? בטח למה לא: https://codesandbox.io/s/restless-night-u4ukmn?file=/src/App.tsx:186-347

טיפ דוקר: סדר פעולות ב Dockerfile

14/03/2022

קובץ ה Dockerfile הבא מגדיר בניה של אפליקציית Rails שגם מכילה קוד צד לקוח, שבתורו מתקמפל עם node.js:

FROM ruby:2.7

WORKDIR /app
COPY . .

ENV RAILS_ENV=production
ENV NODE_ENV=production

RUN gem install bundler:2.2.5
RUN sh -c "apt-get update && apt-get install -y nodejs npm"

RUN bundle install

RUN sh -c "cd client; npm install"
RUN "./bin/rails assets:precompile"

CMD ["./bin/rails", "s"]

רואים מה הבעיה כאן?

המשך קריאה

שלוש סיבות למה לא כדאי להכריח פריימוורקס לעבוד כמו שאתם רוצים

13/03/2022

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

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

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

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

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

היום למדתי: הסלקטור focus-within

12/03/2022

סלקטור מאוד שימושי ב CSS שגיליתי רק לאחרונה הוא :focus-within. הוא מאפשר לזהות מתי פוקוס נמצא על אלמנט או על אחד הילדים שלו.

ולמה זה טוב? נניח שאנחנו בונים קומפוננטה מורכבת ב HTML/CSS. בואו נגיד שזו תיבת טקסט לבחירת תגיות, שכשלוחצים על התיבה מופיעה רשימה של כל התגיות לבחירה. מבחינת הפוקוס, לחיצה על תיבת הטקסט נותנת לה את הפוקוס, ואחרי זה לחיצה על כל אחד מהפריטים מעבירה את הפוקוס לפריט שנלחץ. אם תיבת הטקסט ורשימת הפריטים נמצאים בתוך אותו div, אז אני יכול להשתמש ב focus-within על הדיב העוטף הראשי כדי להחליט אם להציג או להסתיר את רשימת האפשרויות.

בקוד ה HTML יראה כך:

<div class="multiselect">
  <label>
      Click To Select:
      <input type="text" />
  </label>

  <ul>
    <li><label tabindex="0"><input type="checkbox" />item 1</label></li>
    <li><label tabindex="0"><input type="checkbox" />item 2</label></li>
    <li><label tabindex="0"><input type="checkbox" />item 3</label></li>
    <li><label tabindex="0"><input type="checkbox" />item 4</label></li>
    <li><label tabindex="0"><input type="checkbox" />item 5</label></li>
  </ul>
</div>

<div class="multiselect">
  <label>
      Click To Select:
      <input type="text" />
  </label>

  <ul>
    <li><label tabindex="0"><input type="checkbox" />item 1</label></li>
    <li><label tabindex="0"><input type="checkbox" />item 2</label></li>
    <li><label tabindex="0"><input type="checkbox" />item 3</label></li>
    <li><label tabindex="0"><input type="checkbox" />item 4</label></li>
    <li><label tabindex="0"><input type="checkbox" />item 5</label></li>
  </ul>
</div>

<div class="multiselect">
  <label>
      Click To Select:
      <input type="text" />
  </label>

  <ul>
    <li><label tabindex="0"><input type="checkbox" />item 1</label></li>
    <li><label tabindex="0"><input type="checkbox" />item 2</label></li>
    <li><label tabindex="0"><input type="checkbox" />item 3</label></li>
    <li><label tabindex="0"><input type="checkbox" />item 4</label></li>
    <li><label tabindex="0"><input type="checkbox" />item 5</label></li>
  </ul>
</div>

וה CSS יהיה:

.multiselect:focus-within {
  background: lightblue;
}

.multiselect ul {
  display: none;
}

.multiselect label {
  display: block;
  width: 100%;
}

.multiselect:focus-within ul {
  display: block;
}

והתוצאה live בקודפן היא:

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

בעולם האמיתי

11/03/2022

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

a = [10, 20, 33, 51, 94, 100]
b = [2, 3, 99, 102, 500]

צריך להדפיס את הרשימה:

[2, 3, 10, 20, 33, 51, 94, 99, 100, 102, 500]

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

def test1():
    return sorted([*a, *b])

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

def test2():
    i = 0
    j = 0
    output = []
    while True:
        if i >= len(a):
            output.extend(b[j:])
            break

        if j >= len(b):
            output.extend(a[i:])
            break

        next_a = a[i]
        next_b = b[j]

        if next_a < next_b:
            i += 1
            output.append(next_a)
        else:
            j += 1
            output.append(next_b)

    return output

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

print("Short solution took - ", timeit.timeit("test1()", globals=locals()))
print("Long smart solution tool - ", timeit.timeit("test2()", globals=locals()))

והתוצאה הלא ממש מפתיעה:

Short solution took -  0.26264833300956525
Long smart solution tool -  1.3138192089973018

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

בוא ננסה ונראה מה יקרה

10/03/2022

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

בתיאוריה אין בזה שום סיכון...

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

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

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

הלימוד הכואב

09/03/2022

יש שני סוגים מרכזיים של לימוד-

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

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

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

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

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

מדריך: הקמת שרת לוגים עם syslog-ng

08/03/2022

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

המשך קריאה