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

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

זאת שעושה פחות וזאת שעושה יותר

28/10/2020

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

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

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

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

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

זה כנראה לא היה המשרד

27/10/2020

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

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

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

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

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

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

לך בעקבות הריח

26/10/2020

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

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

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

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

export * from "./Components/Icon/Icon";

ו-בום.

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

ריאקט, כלים אוטומטיים ו Strict Mode

25/10/2020

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

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

function Counter(props) {
    const [count, setCount] = useState(0);

    console.count("Counter::render");

    function handleClick() {
        setCount(c => c + 1);
        setCount(c => c + 1);
    }

    return <button onClick={handleClick}>Clicks: {count}</button>;
}

התוצאה בקונסול צריכה להיות הדפסה פעם אחת של הטקסט Counter::render, למרות שקראנו ל setCount פעמיים. זה קורה בגלל ש React מחכה עם העדכון עד שכל פקודות ה set ייגמרו. אבל אם הסיפור היה נגמר כאן לא היה מה להכין דוגמה עליו; מה שמעניין בריאקט זה שאם הקוד היה נקרא כתוצאה מ setTimeout במקום כתוצאה מלחיצה על כפתור, אז ריאקט כבר כן היה קורא ל render פעמיים במקום לאחד את ה set-ים (התנהגות מתועדת ועם הבטחה לאי תאימות בעתיד - כלומר אל תבנו על זה שזה יישאר ככה).

אבל אנחנו לא כאן בשביל לדבר על ריאקט אלא על הדוגמה שלי שלא עבדה. לא משנה כמה התאמצתי הקומפוננטה שלי הדפיסה על המסך Counter::render פעמיים!. בדיוק כשחשבתי לוותר הלכתי להעיף מבט בקוד של index.js שנוצר אוטומטית עם create-react-app:

  ReactDOM.render(
     <React.StrictMode>
       {app}
     </React.StrictMode>,
    document.getElementById('root')
  );

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

StrictMode renders components twice (on dev but not production) in order to detect any problems with your code and warn you about them (which can be quite useful).

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

שני סוגי שיקולים

24/10/2020

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

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

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

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

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

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

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

איך להתמודד עם מחסום כתיבה

23/10/2020

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

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

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

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

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

רק אני איטי?

22/10/2020

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

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

ואז אני נזכר-

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

חידת ראיונות עבודה: הפונקציה Array.fill ואוביקטים ב JavaScript

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

אנחנו יודעים שהפונקציה Array.prototype.fill ממלאת מערך בערך שנעביר לה, ולכן הקוד הבא מחזיר 40:

new Array(4).fill(10).reduce((acc, val) => acc + val);

ואנחנו גם יודעים שאפשר לשנות חלק מהערכים אחרי שהפעלנו fill כך שלדוגמה הקוד הבא יחזיר 50:

const data = new Array(4).fill(10);
data[0] += 10;

data.reduce((acc, val) => acc + val);

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

data.reduce((acc, val) => acc + val.value, 0)

קל ופשוט

20/10/2020

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

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

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

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

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

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

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

(ותודה לריק היקי על ההשראה ולויטאלי ששיכנע אותי להקשיב לו).

היום למדתי: משתנה הסביבה INIT_CWD ב npm

19/10/2020

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

.
├── foo
│   └── bar
│       └── buz
└── package.json

ובקובץ package.json התוכן הבא:

{
  "name": "npmdemo",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "test": "echo Running from $(realpath .)"
  },
  "keywords": [],
  "author": "",
  "license": "ISC"
}

אז אני יכול מתוך תיקיה פנימית בפרויקט להריץ:

 ~/tmp/npmdemo/foo/bar/buz  npm test

> npmdemo@1.0.0 test /home/ynon/tmp/npmdemo
> echo Running from $(realpath .)

Running from /home/ynon/tmp/npmdemo

והסקריפט ירוץ מהתיקיה הראשית של הפרויקט, בלי שאכפת למישהו שאני הייתי בתיקיית foo/bar/buz כשהרצתי את הפקודה.

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

בשביל זה בדיוק npm מגדיר משתנה סביבה בשם INIT_CWD שכולל את הנתיב המלא לתיקיה ממנה הרצתי את פקודת ה npm. בשביל לראות את זה אעדכן את package.json לתוכן הבא:

{
  "name": "npmdemo",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "test": "echo Running from $(realpath .)",
    "test-here": "cd $INIT_CWD; echo Running from $(realpath .)"
  },
  "keywords": [],
  "author": "",
  "license": "ISC"
}

ועכשיו בהרצה אני מקבל:

 ~/tmp/npmdemo/foo/bar/buz  npm run test

> npmdemo@1.0.0 test /home/ynon/tmp/npmdemo
> echo Running from $(realpath .)

Running from /home/ynon/tmp/npmdemo
 ~/tmp/npmdemo/foo/bar/buz  npm run test-here

> npmdemo@1.0.0 test-here /home/ynon/tmp/npmdemo
> cd $INIT_CWD; echo Running from $(realpath .)

Running from /home/ynon/tmp/npmdemo/foo/bar/buz