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

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

ריאקט 18 סוף סוף מציע פיתרון לבעיית ה label-ים

02/07/2022

עד שהגיע ריאקט או בכלל הרעיון של קומפוננטות, מתכנתים ומתכנתות כתבו קבצי HTML גדולים ובתוכם היו label-ים ו input-ים המתואמים ביניהם באמצעות id:

<label for="name">User Name</label>
<input type="text" id="name" />

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

<label>
User Name
<input type="text" />
</label>

ועד דברים יותר מתוחכמים כמו המצאת מזהים:

function MyForm() {
    const id = Math.random().toString(16);
    return (
        <>
            <label htmlFor={id}>User Name</label>
            <input type="text" id={id} />
        </>
    );
}

הבעיה בפיתרון הראשון היא שהוא מכריח markup מסוים. כל עוד זה עובד לכם הכל טוב, אבל לפעמים באמת ה label וה input לא צמודים אחד לשני. הבעיה בפיתרון השני היא שכנראה לא נקבל את אותו id ב Server Side Rendering, מה שייצור אי תאימות כשריאקט יריץ את כל ה render-ים בדפדפן ויקבל HTML עם מזהים שונים.

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

import { useId } from 'react';

function MyForm() {
    const id = useId();
    return (
        <>
            <label htmlFor={id}>User Name</label>
            <input type="text" id={id} />
        </>
    );
}

ה id שמקבלים לא יצירתי במיוחד ובדוגמה שניסיתי קיבלתי מחרוזת כמו :r1:, :r3 ו :r5:. אני לא יודע למה דווקא r ולמה הוא מדלג על הזוגיים. אם יש לכם רעיון או מידע פנימי אשמח לשמוע בתגובות.

מודל מנטאלי

01/07/2022

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

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

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

השאלה האמיתית (בכל לימוד), היא לא "מה עושה פקודה X או Y", אלא "מה המודל המנטאלי שאני צריך כדי להבין את הכלי הזה".

היום למדתי: הקשר בין globals לניקוי ה DOM ב vitest

30/06/2022

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

import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react'

// https://vitejs.dev/config/
export default defineConfig({
  plugins: [react()],
  test: {
    globals: true,
    environment: "jsdom",
  },
})

אז בכל קבצי הבדיקות שלכם תוכלו להסתמך על זה שיש describe, it ו expect ואולי עוד כמה משתנים גלובאליים. ברירת המחדל היא false ואז צריך לייבא הכל לבד עם שורה כזאת בתחילת קובץ בדיקות:

import { describe, it, expect } from 'vitest';

עד לפה אין סיבה להתרגש, אבל מסתבר שלהזרקת ה globals יש עוד אפקט והוא הרבה יותר מורגש: בעת בדיקת קומפוננטות ריאקט עם react-testing-library, הספריה תנקה את ה DOM בין בדיקה לבדיקה אם ה globals מוזרקים, אבל לא תנקה בלעדיהם.

במילים אחרות בקובץ בדיקות כזה:

import { describe, it, expect } from 'vitest';
import { screen, render } from '@testing-library/react';
import App from './App';

describe('App Changes Colors', () => {
  it('shows a button', () => {
    render(<App />);
    const btn = screen.getByRole('button', { name: '0' });
    expect(btn).toBeTruthy();
  });
  it('shows a button', () => {
    render(<App />);
    const btn = screen.getByRole('button', { name: '0' });
    expect(btn).toBeTruthy();
  });
});

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

הפונקציה הרלוונטית מ testing-library שמטפלת בסיפור המחיקה נקראת cleanup וזה מה שמוסבר גם בתיעוד שלה:

    Please note that this is done automatically if the testing framework you're using supports the afterEach global and it is injected to your testing environment (like mocha, Jest, and Jasmine). If not, you will need to do manual cleanups after each test.

לכן אם אתם עובדים ב vitest בלי הזרקת גלובאליים תצטרכו לזכור להפעיל את cleanup בעצמכם. אבל יותר קל פשוט להדליק את הזרקת הגלובאליים ולא לחשוב על זה (עד העקיצה הבאה).

טיפ ריאקט: איך לא להשתמש ב useDeferred

29/06/2022

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

נתבונן בקוד הבא:

function BrokenHugeList() {
  const [search, setSearch] = useState('');
  const deferredSearch = useDeferredValue(search, { timeoutMs: 5000 });

  function handleChange(e) {
    setSearch(e.target.value);
  }
  console.log(`1 seach = ${deferredSearch}`);
  const searchResults = items.filter(i => i.includes(deferredSearch));
  console.log('2');

  return (
    <div>
      <input type="text" value={search} onChange={handleChange} />
      <ul>
        {searchResults.map(i => <li key={i}>{i}</li>)}
      </ul>
    </div>
  );
}

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

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

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

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

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

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

const HugeList = React.memo(function HugeList(props) {
  const { search } = props;
  console.log(`3, search = ${search}`);
  const searchResults = items.filter(i => i.includes(search));
  console.log('4');

  return (
    <div>
      <ul>
        {searchResults.map(i => <li key={i}>{i}</li>)}
      </ul>
    </div>
  );
});

function App() {
  const [search, setSearch] = useState('');
  const deferredSearch = useDeferredValue(search, { timeoutMs: 5000 });

  function handleChange(e) {
    setSearch(e.target.value);
  }

  return (
    <>
      <input type="text" value={search} onChange={handleChange} /> 
      <HugeList search={deferredSearch} />
      <hr />
    </>
  );
}

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

התמונה הגדולה

28/06/2022

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

אבל הערך האמיתי הוא בתמונה הגדולה - למה בכלל אנחנו בודקים? איזה ערך מקבלים מכל סוג בדיקות? כמה זה עולה לנו?

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

היכרות עם Redux Toolkit

27/06/2022

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

המשך קריאה

המחיר של יצירתיות

26/06/2022

השקענו כבר כל כך הרבה...

כל הלקוחות שלנו מחכים לפיצ'ר הזה...

זה ממש כמעט עובד...


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

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

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


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

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

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

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

לולאות באגים

24/06/2022

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

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

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

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

נקסט!

טיפים לגיוס מוצלח דרך upwork

23/06/2022

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

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

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

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

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

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

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

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

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

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