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

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

גם בעבודה עם ריאקט - Tailwind CSS היא רעיון מעולה

15/01/2023

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

והכח של טיילווינד ביישומי ריאקט הוא שזה פשוט פיתרון הרבה יותר קל והרבה יותר טוב מכתיבת Inline Styles.

בואו נראה איך זה עובד ואיך לשלב את טיילווינד ביישום שלכם.

המשך קריאה

קוד לשימוש חוזר

14/01/2023

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

דוגמה? בשמחה. קחו קוד פייתון שעושה יותר מדי עבודה על משפט:

import re

text = "one two three I want to see three four five I am alive"

# delete all spaces in sentences
text = re.sub(r'\s+', '', text)

# replace each letter by its ordinal value
text = [ord(c) for c in text]

# leave only values that are larger than what came before
new_text = [text[0]]
for value in text:
    if value > new_text[-1]:
        new_text.append(value)

text = new_text

print(sum(text))

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

גירסה מסוכנת יותר של הקוד הזה עשויה להיראות כך:

import re

def main():
    text = "one two three I want to see three four five I am alive"
    text = delete_all_spaces(text)
    text = change_to_ord_values(text)
    text = change_to_increasing(text)
    print_result(text)


def delete_all_spaces(text):
    # delete all spaces in sentences
    text = re.sub(r'\s+', '', text)
    return text

def change_to_ord_values(text):
    # replace each letter by its ordinal value
    text = [ord(c) for c in text]
    return text

def change_to_increasing(text):
    # leave only values that are larger than what came before
    new_text = [text[0]]
    for value in text:
        if value > new_text[-1]:
            new_text.append(value)

    text = new_text
    return text

def print_result(text):
    print(sum(text))


main()

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

רוב הסיכויים שלא.

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

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

מחקר ופיתוח

12/01/2023

בכל החברות אוהבים לקרוא למחלקת תוכנה R&D, קיצור של מחקר ופיתוח. הם רק שכחו להגיד לנו ששני הדברים האלה שונים ודורשים התנהלות שונה:

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

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

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

דו לשוני

11/01/2023

המוכרת בחנות הירקות בברצלונה הסתכלה עליי כאילו נחתתי מאיזה כוכב אחר - Cucmber? Que es eso? היא שאלה, והיה ברור לאן הגעתי. למרות שכל שלט, כל הודעה, כל מכונת כרטיסים וכל היבט אחר של החיים בעיר מתנהל ב-3 שפות (אנגלית, קטלאנית וספרדית), החוליה החלשה היא האנשים שכמו בכל מקום אוהבים את אזור הנוחות שלהם.

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

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

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

זה ייקח רק דקה

10/01/2023

זה ייקח רק דקה? באמת?

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

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

עדיפה ארכיטקטורה טובה על מעקף טוב

09/01/2023

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

function CountLabel({ count }) {
  const [prevCount, setPrevCount] = useState(count);
  const [trend, setTrend] = useState(null);
  if (prevCount !== count) {
    setPrevCount(count);
    setTrend(count > prevCount ? 'increasing' : 'decreasing');
  }
  return (
    <>
      <h1>{count}</h1>
      {trend && <p>The count is {trend}</p>}
    </>
  );
}

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

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

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

import { useState } from "react";

function useTrendedState(initialValue) {
  const [value, setValue] = useState(initialValue);
  const [prevValue, setPrevValue] = useState(initialValue);

  function setter(...args) {
    setPrevValue(value);
    return setValue(...args);
  }

  return [value, setter, prevValue];
}

function CountLabel({ count, prevCount }) {
  let trend;
  if (count < prevCount) {
    trend = "Decreasing";
  } else if (count > prevCount) {
    trend = "Increasing";
  } else {
    trend = "Stable";
  }

  return (
    <>
      <h1>{count}</h1>
      {trend && <p>The count is {trend}</p>}
    </>
  );
}

export default function App() {
  const [count, setCount, prevCount] = useTrendedState(0);
  return (
    <>
      <button onClick={() => setCount(count + 1)}>Increment</button>
      <button onClick={() => setCount(count - 1)}>Decrement</button>
      <CountLabel count={count} prevCount={prevCount} />
    </>
  );
}

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

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

פרסים

08/01/2023

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

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

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

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

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

איך לבטל מיזוג של ענף ישן ב git

07/01/2023

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

המשך קריאה

אמא שלך שברה את המיילים

06/01/2023

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

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

<input type="email" />

אבל בגירסה החדשה (עד אתמול) היא היתה מסוג טקסט:

<input type="text" />

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

עד לפני שבוע בערך.

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

מסתבר שספריית mailgun_rails בה אני משתמש כדי לשלוח את המיילים רגישה לכתובות אימייל לא חוקיות. כששלחתי דרכה למיילגאן את רשימת הכתובות להפצה של הפוסט היומי, הקוד התרסק כי "אמא שלך" היא לא כתובת מייל תקנית. הבאג ישן ומפורט בריטהאב כאן: https://github.com/jorgemanrubia/mailgun_rails/issues/48

שלושה לקחים מהסיפור הזה ששווה גם לכם ליישם:

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

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

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

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