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

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

איך בוחרים בין הגיוני למועיל?

02/05/2024

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

אז עכשיו אנחנו יכולים להגדיר את הטיפוס:

create(o: object | null): any;

ורגע אם כבר הגדרנו את create אפשר להמשיך לרוץ עם אותו רעיון ולהגדיר גם את:

setPrototypeOf(o: any, proto: object | null): any;

ויש עוד כמה הגדרות שאפשר למצוא ב es5.d.ts שהמשותף לכולן הוא ה object | null. בעצם יש רק מקום אחד ש object מופיע בלי החיבור ל null וזה הטיפוס של הפונקציה Object.keys:

keys(o: object): string[];

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

בחזרה לאתגר שלנו - למה null הוא לא חלק מ object? למה להגדיר טיפוס חדש כשכל פעם שמשתמשים בו זה כחלק מהחיבור object | null?

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

typeof null === 'object'

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

היום למדתי: nth-child וקלאס ב CSS

01/05/2024

נניח שיצרתם טבלה עם CSS Grid ועכשיו אתם רוצים לצבוע רק שורה מסוימת - או יותר טוב, להדגיש את הגבול מסביב לשורה מסוימת. בעולם הישן של table היה מספיק למצוא את ה tr שמתאים לשורה ולהגדיר לו גבול, אבל בגריד הטבלה לא כוללת אלמנט tr. כל ה HTML שלה הוא בסך הכל:

<div class='container'>
  <div>0</div>
  <div>1</div>
  <div>2</div>
  <div>3</div>
  <div>4</div>
  <div>5</div>
  <div>6</div>
  <div>7</div>
  <div>8</div>
  <div>6</div>
  <div>7</div>
  <div>8</div>
</div>

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

<div class='container'>
  <div>0</div>
  <div>1</div>
  <div>2</div>
  <div class='selected-row'>3</div>
  <div class='selected-row'>4</div>
  <div class='selected-row'>5</div>
  <div>6</div>
  <div>7</div>
  <div>8</div>
  <div>6</div>
  <div>7</div>
  <div>8</div>
</div>

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

.container .selected-row{
  border-top-color: red;
  border-bottom-color: red;
}

ושני התאים בקצה? אפשר היה לדמיין להשתמש פה ב nth-child(1):

.container .selected-row:nth-child(1) {
  border-left-color: red;
}

אבל זה לא עובד. ה div הראשון עם הקלאס selected-row הוא לא הילד הראשון ולכן כלום לא נצבע. מה עושים? מסתבר שיש ל nth-child טריק כדי לתפוס את הילד ה n-י שמתאים לקלאס מסוים וזה נראה ככה:

.container :nth-child(1 of .selected-row) {
  border-left-color: red;
}

.container :nth-child(3 of .selected-row) {
  border-right-color: red;
}

הדוגמה כאן למי שרוצה לשחק עם זה: https://codepen.io/ynonp/pen/zYXQqmO

מה בעצם אנחנו פוחדים לאבד?

29/04/2024

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

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

דרך אחרת להסתכל על הבעיה היא לשנות את השאלה - במקום לשאול איך אני רוצה לשפר את המצב אני אשאל "מה בעצם אני פוחד לאבד פה?", מה ה Best Case שיקרה אם אשאר?

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

מטלות והזדמנויות

28/04/2024

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

מול-

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

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

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

אם ערך חוזר ואף אחד לא מסתכל עליו, הוא עדיין משמיע רעש?

27/04/2024

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

def twice(x): x * 2

ברובי זה לא היה קורה. הפונקציה הזאת ברובי מחזירה בדיוק את מה שרציתי - כלומר את המספר שקיבלה כפול 2:

def twice(x) = x * 2

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

def fib_rb(n)
  a = 0
  b = 1
  n.times { a, b = b, a + b }
  a
end

בדוגמה הזאת הבלוק (הקוד שרץ n פעמים מוקף בסוגריים מסולסלים) בעצם עושה את זה:

def fib_rb(n)
  a = 0
  b = 1
  n.times {
    a, b = b, a + b
    return [b, a + b]
  }
  a
end

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

def fib_rb(n)
  a = 0
  b = 1
  n.times { a, b = b, a + b; nil }
  a
end

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

טיפ ריאקט: איך לתפוס לחיצה מחוץ לקומפוננטה

26/04/2024

חבר שואל - יש לי קומפוננטה שפותחת תפריט קופץ. איך אני מעלים את התפריט כשמשתמש לוחץ על המסך מחוץ לקומפוננטה? יש משהו כמו onClick אבל על כל שאר הדברים?

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

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

סך הכל הקוד נראה כך:

import React, { useEffect, useState } from 'react';

const initialText = "Click outside the component to change this text"

export function App(props) {
  const [text, setText] = useState(initialText);

  useEffect(() => {
    const changeText = () => setText('Yay!');
    window.addEventListener('click', changeText);

    return () => {
      window.removeEventListener(changeText);
    }
  }, []);

  return (
    <div
      className='App'
      style={{background: 'red'}}
      onClick={(e) => e.stopPropagation()}
    >
      <h2>{text}</h2>
    </div>
  );
}

מוזמנים לשחק עם הקוד לייב בקודפליי בקישור: https://playcode.io/1848762

טרנזאקציות

25/04/2024

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

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

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

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

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

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

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

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

יום 17 של Advent Of Code 2023 - היום בו סקאלה עבדה נגדי

24/04/2024

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

המשך קריאה

פיתוח קוד שמתאים גם ל Node וגם ל Deno

23/04/2024

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

המשך קריאה