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

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

האומנות של להשיג דברים בלתי אפשריים

15/12/2021

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

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

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

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

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

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

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

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

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

מושגים בסיסיים בקוברנטיס

14/12/2021

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

המשך קריאה

היום למדתי: CSS Sticky ו overflow לא הולכים טוב יחד

13/12/2021

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

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

<div class="root">
<table>
  <thead>
    <tr>
    <th>name</th>
    <th>hair color</th>
    <th>city</th>
    <th>gender</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>bob</td>
      <td>blue</td>
      <td>foo</td>
      <td>agender</td>
    </tr>
    <tr>
      <td>bob</td>
      <td>blue</td>
      <td>foo</td>
      <td>agender</td>
    </tr>
</tbody>
<table>
</div>
.root {
  overflow: hidden;
}
thead th {
  position: sticky;
  top: 0;
  background: #d2d2d2;  
  padding: 2px 5px 2px 0;

}

table {
  border-collapse: collapse;
}

ובקודפן:

וקל לראות שגלילה של המסך לא באמת משאירה את שורת הכותרת "דבוקה" לראש העמוד. מה קורה פה?

התשובה מסתתרת במשפט הבא מתוך MDN:

Note that a sticky element "sticks" to its nearest ancestor that has a "scrolling mechanism" (created when overflow is hidden, scroll, auto, or overlay)

או בעברית - בגלל שמעל הטבלה יש לי div עם מאפיין overflow: hidden, למרות של div הזה אין הגבלת גובה והוא לא מייצר גלילה פנימית, ה sticky של הטבלה יהיה דביק ביחס אליו ולא ביחס ל body. במילים אחרות שורת הכותרת של הטבלה אכן נדבקת לחלק העליון, פשוט לחלק העליון של ה div שעוטף אותה, והוא זז למעלה מחוץ לחלק שאנחנו רואים.

הפיתרון? כמו תמיד פשוט כשיודעים. כל פעם שרוצים להשתמש ב position: sticky יש לוודא שאין מעליכם אלמנט עם overflow. הנה הקודפן המתוקן:

מתי מתחילים ואיפה מוצאים זמן?

12/12/2021

אלה שתי שאלות שונות. אל תנסו לחבר ביניהן.

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

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

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

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

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

מקום ריק

11/12/2021

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

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

לקח לי זמן להבין שהפיתרון הזה היה יותר גרוע מהבעיה.

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

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

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

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

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

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

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

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

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

למה לא לקחתי את דן אברמוב לעבודה

10/12/2021

לא מזמן ראיתי את הוידאו של בן עוואד שעושה ראיון עבודה פיקטיבי לדן אברמוב. אם לא ראיתם עדיין ובא לכם לראות את דן אברמוב נאבק עם CSS שווה להיכנס לכאן: https://www.youtube.com/watch?v=XEt09iK8IXs.

אבל הוויכוח שלי עם דן אברמוב לא היה על CSS אלא על נקודה יותר עדינה של אבטחת מידע. באותו ראיון עוואד שואל את אברמוב מה דעתו על dangerouslySetInnerHTML ואברמוב מגיב באי נחת ניכרת. שניהם מסכימים שמשהו ב API הזה מסורבל וההסבר של אברמוב הוא מעניין. נזכיר רק שב API של ריאקט הם רצו להיות מאוד ברורים כשמישהו פותח דלת לפירצת XSS ולכן במקום לקרוא למאפיין שמעדכן את ה innerHTML של אלמנט במשהו פשוט כמו setInnerHTML הם הוסיפו את המילה dangerously. אבל זה לא הכל. כי בשביל להגדיר את ה innerHTML צריך להעביר למאפיין dangerouslySetInnerHTML אוביקט עם מפתח בשם מיוחד __html, כלומר משהו כזה:

<div dangerouslySetInnerHTML={{ __html: "Hello" }}></div>

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

<div html={{ __dangerousHTML: "Hello" }}></div>

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

// file utils.js
export function alertHtml() {
    return { __html: "<b>alert</b>" };
}

ובקובץ שני הקטע:

import { alertHtml } from './utils.js';

<div dangerouslySetInnerHTML={alertHtml()}></div>

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

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

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

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

  2. אם אני צריך לפנות לבסיס הנתונים, אני אעשה Quoting לקלט החיצוני ממש לפני שליחת השאילתה לבסיס הנתונים (רצוי באמצעות שימוש ב Bound Variables).

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

מה קורה כשמושכים מאגר שמישהו שינה מרחוק

09/12/2021

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

המשך קריאה

שלוש סיבות שהתוכנית שלך לא עובדת

08/12/2021

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

המשך קריאה

ואולי דווקא יש פיתרון קל

07/12/2021

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

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

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

טיפ JavaScript: בדיקת פרמטרים והודעות שגיאה

06/12/2021

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

function init(options={}) {
  let redisHost = 'localhost', redisPort = 6379;

  if (options.redis && options.redis.host) {
    redisHost = options.redis.host;
  }
  if (options.redis && options.redis.port) {
    redisPort = options.redis.port;
  }
  console.log(`Connecting to redis at ${redisHost}:${redisPort}`);
}

init();
init({ redis: { host: 'my-redis' } });

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

init({ redis: { host: process.env.REDS_HOST } });

במצב כזה למרות שהעברתי ערך למשתנה redis.host הפונקציה תתעלם מהערך בגלל שהוא undefined ותשתמש בערך ברירת המחדל. שום הודעת שגיאה לא תופיע ולא יהיה לנו שום רמז שכתבנו משתנה בשגיאת כתיב.

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

function init(options={}) {
  let redisHost = 'localhost', redisPort = 6379;

  if (options.redis) {
    if ('host' in options.redis) {
      if (options.redis.host) {
        redisHost = options.redis.host;
      } else {
        throw 'Error: options.redis.host is undefined';
      }
    }
    if ('port' in options.redis) {
      if (options.redis.port) {
        redisPort = options.redis.port;
      } else {
        throw 'options.redis.port is undefined';
      }
    }
  }

  console.log(`Connecting to redis at ${redisHost}:${redisPort}`);
}

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