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

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

אופס הכנסתי קונסטריינט

09/11/2023

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

def createPost(g: GraphTraversalSource, slug: String, title: String, publishedAt: String): Vertex =
  g.addV("post")
    .property("slug", slug)
    .property("title", title)
    .property("publishedAt", LocalDateTime.parse(publishedAt, formatter))
    .next()

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

גרמלין היא שפת שאילתות ואפשר להשתמש בה כדי לתשאל בסיסי נתונים מרוחקים (למשל neptune שרץ בענן של AWS או קוסמוס שרץ באז'ור או ארקייד דיבי שרץ על מכונה שלכם), ובנוסף כדי לגשת לבסיסי נתונים בזיכרון שרצים באותו JVM כמו התוכנית.

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

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

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

קוד מושלם

08/11/2023

אם הייתי יודע איך יראו כל הדרישות העתידיות מהמערכת הייתי שמח לכתוב את הקוד המושלם.

בעולם האמיתי אני מעדיף להתמקד בקוד שקל לשנות אותו.

יש לי שלושה ימים עד הראיון עבודה בריאקט - מה ללמוד?

07/11/2023

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

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

  2. הרבה אנשים מדברים על ביצועים ועל הוקים כמו useCallback, אבל פחות ממה שחשבתי. כמעט אף אחד לא מתייחס ל Concurrent Mode ול useTransition

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

  4. הרבה אנשים כן דיברו על החשיבות של פיתוח Front End באופן כללי - אם זה טייפסקריפט, היכרות טובה עם JavaScript ויכולות חדשות של השפה, ואפילו CSS ויכולות חדשות שלו.

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

תיעדוף

05/11/2023

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

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

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

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

  2. עם הזמן בעיות מסובכות צוברות "ריבית" והופכות ליותר מסובכות.

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

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

לא חייבים לפתור את כל הבעיות. חשוב לפתור את החשובות.

מדריך וידאו חדש: פיתוח יישומי Full Stack React עם Next.JS 14

04/11/2023

לפני שבוע יצאה רשמית גירסה 14 של next.js. שתי הגירסאות האחרונות - 13 ו 14, התמקדו ביצירת תשתית לפיתוח יישומי Full Stack ולאינטגרציה בין קוד צד שרת לקוד צד לקוח.

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

הרעיון החדש הראשון שנכנס עוד ב next 13 הוא קומפוננטות צד שרת, או יותר נכון קומפוננטות צד-שרת בלבד. אלה קומפוננטות ריאקט שעוברות Server Side Rendering אבל לא עוברות Hydration בדפדפן כך שאין צורך לשלוח את ה JavaScript שלהן לדפדפן. קומפוננטות אלה מוגדרות על ידי פונקציות אסינכרוניות ויכולות להשתמש בכל היכולות של קוד צד שרת, כלומר משהו בסגנון הזה:

export default () => {
    const users = await listUsers();
    return (
        <p>{users.length} connected users</p>
    );
};

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

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

export default () => {
    const login = async () => {
        const isConnected = await checkUser();
    };

    return (
        <button onClick={login}>Login</button>
    );
};

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

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

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

המדריך סגור למנויי האתר בלבד אז אם אתם כבר מנויים פשוט נכנסים ללינק, ואם לא אז אולי זאת הזדמנות טובה להצטרף (וגם זה בלחיצה על אותו לינק): https://www.tocode.co.il/boosters/next14-fullstack-react.

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

רגעים של הארה

03/11/2023

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

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

חשבו על הארות כמו "אהה - עכשיו אני מבין למה צריך Virtual DOM" או "אהה - עכשיו אני מבין איך אפשר לחיות בלעדיו" ובעיקר "עכשיו אני מבין למה מה שניסיתי לעשות לא יכול לעבוד".

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

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

לגזור ולשמור: איך להוריד את Google Chrome ו chromedriver מתאים מתוך Dockerfile ב 2023

02/11/2023

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

ומאחר ואחת המטרות של הבלוג הזה היא לעזור לי לשמור דברים חשובים כדי שיהיה לי קל למצוא אותם אחרי (וכן גם לשתף אתכם באותם הדברים כדי שלא תצטרכו לבזבז את הזמן שאני בזבזתי) זה ה Dockerfile שעבד לי נכון לנובמבר 2023 כדי להתקין ולהריץ כרום במצב ללא ראש על אובונטו:

FROM ubuntu:23.04

WORKDIR /app

RUN apt-get update &&\
    apt-get install -y xvfb gnupg wget curl unzip jq ca-certificates --no-install-recommends &&\
    wget -q -O - https://dl-ssl.google.com/linux/linux_signing_key.pub | apt-key add - &&\
    echo "deb http://dl.google.com/linux/chrome/deb/ stable main" >> /etc/apt/sources.list.d/google.list &&\
    apt-get update -y && \
    apt-get install -y google-chrome-stable && \
    export CHROMEDRIVER_URL=$(curl https://googlechromelabs.github.io/chrome-for-testing/last-known-good-versions-with-downloads.json|jq -r '.channels.Stable.downloads.chromedriver | .[] | select(.platform == "linux64").url') &&\
    wget -q --continue -P /chromedriver $CHROMEDRIVER_URL &&\
    unzip /chromedriver/chromedriver* -d /chromedriver

# make the chromedriver executable and move it to default selenium path.
RUN chmod +x /chromedriver/chromedriver-linux64/chromedriver
RUN mv /chromedriver/chromedriver-linux64/chromedriver /usr/bin/chromedriver


RUN groupadd -r user && useradd -m -r -g user user && chown -R user /app
USER user

EXPOSE 9222

# default entry point.
CMD ["/usr/bin/google-chrome", "--headless", "--disable-gpu", "--disable-software-rasterizer", "--remote-debugging-port=9222", "--remote-debugging-address=0.0.0.0", "https://www.google.com"]

המשך קריאה

היום למדתי: CSS Nesting כבר עובד בדפדפן

01/11/2023

איפשהו בעבר הרחוק מישהו סיפר לי על Less ו Sass ואיך שווה להשתמש בהם כי אפשר לכתוב סלקטור בתוך סלקטור וזה היה מאוד מרגש בשעתו. עם הזמן נפרדנו מ Less ולמדנו לקבל את Sass ונכון שלימים גם הוא הוחלף ב CSS In JS וב Tailwind אבל עדיין יש משהו קסום בלכתוב CSS ויש עדיין לא מעט פרויקטים עם קבצי css.

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

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

קוד HTML:

<form>
  <label for="name">Name:
    <input type="text" id="name" />
  </label>
  <label for="email">email:</label>
  <input type="text" id="email" />
</form>

קוד CSS:

input {
  /* styles for input not in a label  */
  border: tomato 2px solid;
}
label {
  /* styles for label */
  font-family: system-ui;
  font-size: 1.25rem;
  input {
    /* styles for input in a label  */
    border: blue 2px dashed;
  }
}

ולייב קודפן:

https://codepen.io/ynonp/pen/abXZbvo

לכל הפרטים וההבדלים מול Sass שווה להעיף מבט בתיעוד בקישור: https://developer.mozilla.org/en-US/docs/Web/CSS/CSSnesting/UsingCSS_nesting

טיפ JavaScript - ניסוי עם ערך ברירת מחדל

31/10/2023

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

async function sleepOrDie(ms, result) {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      if (Math.random() > 0.5) {
        resolve(result);
      } else {
        reject();
      }
    }, ms)
  })
}

async function handleClick() {
  const value = await sleepOrDie(50, 10);
  alert(value);
}

document.querySelector('button').addEventListener('click', handleClick);

אני יודע לא מתוחכם במיוחד אבל מספיק בשביל הדגמה. אם הפונקציה האסינכרונית מצליחה אז מקבלים ערך, אבל אם היא נכשלת נזרק Exception. זה עובד אם הטיפול שלנו בשגיאות הוא ב flow אחר ואז catch יוכל להפעיל את אותו flow. אבל מה אם אני פשוט רוצה לתת ל value ערך ברירת מחדל אם היתה שגיאה? למשל אם אני מנסה לקרוא ערך מבסיס נתונים או מהגדרות משתמש ואם הערך לא שם הכל בסדר והקוד ממשיך עם אותה ברירת מחדל. במצב כזה try/catch יכול להיראות מסורבל:

async function handleClick() {
  let value = 0;
  try {
    value = await sleepOrDie(50, 10);
  } catch (err) {

  }
  alert(value);
}

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

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

async function tryOr(fn, args, defaultValue) {
  try {
    return await fn.apply(null, args);
  } catch (err) {
    return defaultValue;
  } 
}

async function handleClick() {
  const value = await tryOr(sleepOrDie, [50, 10], 0);
  alert(value);
}

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

Function.prototype.tryOr = async function (args, defaultValue) {
  try {
    return await this.apply(null, args);
  } catch (err) {
    return defaultValue;
  }
}


async function handleClick() {
  const value = await sleepOrDie.tryOr([50, 10], 0);
  alert(value);
}