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

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

עלות שקועה

22/03/2022

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

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

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

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

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

ועכשיו יש לנו החלטה חדשה לקבל-

בהינתן כל מה שאני יודע היום, נניח שמישהו היה מציע לי בחירה בין שני המנגנונים (הבלוג שלי והוגו), איזה מנגנון הייתי בוחר?

האם הייתי מעדיף פיתרון שהושקעו בו שבועיים ועדיין לא נוסה בפרודקשן, או פיתרון שהושקעו בו שנים ויש לו המון משתמשים פעילים?

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

טיפ CSS: המאפיין display: contents

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

במילים אחרות אם יש לי פס עליון באתר ובו 6 לינקים, וה HTML שלי נראה ככה:

<div class="topbar">
    <a href="#">link #1</a>
    <a href="#">link #2</a>
    <a href="#">link #3</a>
    <a href="#">link #4</a>
    <a href="#">link #5</a>
    <a href="#">link #6</a>
</div>

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

.topbar {
  display: flex;
}

.topbar a {
  padding: 0.2rem;
  flex: 1;
}

אבל אם במקרה החלטתי לחלק את הלינקים ב HTML למספר "בלוקים", רק מבחינה סמנטית או כי היה לי יותר נוח לייצר HTML כזה:

<div class="topbar">
   <div class="part1">
    <a href="#">link #1</a>
    <a href="#">link #2</a>
    <a href="#">link #3</a>
  </div>

  <div class="part2">
    <a href="#">link #4</a>
    <a href="#">link #5</a>
  </div>

  <div class="part3">
    <a href="#">link #6</a>
  </div>
</div>

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

הערך contents למאפיין display, שנתמך ברוב הדפדפנים (חוץ מ IE כמובן), מספק דרך קלה "לדלג" על אלמנטים כשמסדרים אותם בתוך פלקסבוקס או גריד. ה CSS הבא יגרום גם ל HTML השני להציג את הלינקים בצורה יפה בתוך פלקסבוקס:

.topbar {
  display: flex;
}

.topbar > div {
  display: contents;
}

.topbar a {
  padding: 0.2rem;
  flex: 1;
}

וככה זה נראה לייב בקודפן:

מערכת ניהול החבילות של דינו

20/03/2022

אם יש משהו שקל למתכנתי Node להסכים עליו זה ש npm שבור. הבעיות המרכזיות בו הן:

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

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

  3. תלות במאגר מרכזי לכל בניה (היוש left-pad)

  4. התקנת חבילות שכוללות אינסוף זבל בנוסף לקבצים שרצית, ורק מנפחות את node_modules (היוש readline)

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

דינו משתמש ב import ולא ב require, אבל השוס הגדול הוא שבמקום להעביר ל import שם של חבילה אנחנו מעבירים את ה URL המלא אליה. כלומר בשביל להשתמש ב lodash בתוכנית דינו אני אכתוב:

import * as _ from 'https://cdn.skypack.dev/-/lodash-es@v4.17.21-rDGl8YjBUjcrrAbjNrmo/dist=es2019,mode=imports/optimized/lodash-es.js';

const squares = _.map(_.range(10), (x) => x * x);
console.log(squares);

וזה כבר פותר חלק גדול מהבעיות של npm:

  1. בגלל שאני מעביר URL, לא צריך ללכת מכות על שמות.

  2. הגדרת הגירסה היא חלק מה URL והיא מאוד ספציפית, כמו בתגיות ה script שהיו לנו פעם בקבצי HTML, לפני שעברנו לוובפאק. וגם אין ולא צריך package.json. גם לא צריך ללמוד את ההבדל בין devDependencies ל peerDependencies ול dependencies רגילים. מה שעושים לו import נטען.

  3. לא צריך מאגר מרכזי.

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

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

$ deno cache --lock=lock.json --lock-write -r demo.js

וזה ייצור לי קובץ בשם lock.json על המחשב עם התוכן:

{
  "https://cdn.skypack.dev/-/lodash-es@v4.17.21-rDGl8YjBUjcrrAbjNrmo/dist=es2019,mode=imports/optimized/lodash-es.js": "4a1b35d34b5e0d3ae68aa46ddaabb9700ccecb75b830974db7c46a271f10daa8"
}

עכשיו כשאני אעביר את התוכנית למכונה אחרת אני מעביר איתה את קובץ ה lock.json שיצרתי, ומפעיל שם:

$ deno cache --lock=lock.json  -r 03-with-lodash.js

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

$ deno run --lock=lock.json --cached-only  03-with-lodash.js

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

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

אפשר לראות את ה vimrc שלך?

19/03/2022
vim

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

כשאנחנו קוראים קבצי הגדרות כאלה של אחרים יש לנו שתי אפשרויות-

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

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

השיטה השניה תביא תוצאות מהר יותר. השניה את התוצאות הטובות יותר.

לא אותה מטרה

18/03/2022

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

כמה דוגמאות ברשותכם-

המשך קריאה

שימו לב: נוד קורא גם את ה 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. המנגנון החדש שלי לא יהיה עקבי עם מנגנונים אחרים קיימים של הפריימוורק, ולפעמים ישבור אותם.

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