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

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

כזה ניסיתי: Webvm.io

22/07/2024

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

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

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

  3. יש תמיכה ברשת דרך שירות שנקרא Tailscale. אני מודה שבינתיים הצלחתי רק לחבר את המכונה לכתובת IP ציבורית, אבל עדיין לא להגיע אליה ב SSH או לצאת ממנה. שני הדברים אמורים להיות אפשריים.

  4. יש תמיכה בעבודה בתור root (נו זה רץ בדפדפן אז אין שום בעיית אבטחה). פשוט מפעילים su וכותבים את הסיסמה password.

  5. יש על המכונה כבר כמה תוכנות כמו python, perl, gcc, vim, ruby. בגלל שלא הצלחתי להפעיל את החיבור לרשת גם לא הצלחתי להתקין תוכנות אחרות.

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

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

תרגילי תכנות בראיונות עבודה

21/07/2024

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

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

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

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

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

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

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

מסע דילוגים

20/07/2024

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

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

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

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

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

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

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

למה וויתרתי על סוליד ב 2024

19/07/2024

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

  1. הגודל - לקחתי פרויקט ריאקט קטן (פחות מ-10 קבצים, חצי מגה של JavaScript כולל הכל) ותרגמתי לסוליד. התוצאה לקחה 400K. נכון זה 20% פחות וזה מרשים והכל, אבל 20% מחצי מגה זה עדיין מעט. יכול להיות שאם היה לי פרויקט ריאקט של 10 מגה הייתי רואה הבדל משמעותי, אבל גם יכול להיות שבפרויקט של 10 מגה החיסכון היה קטן בהרבה מ 20%. בכל מקרה עבור פרויקט קטן החיסכון בגודל לא מורגש.

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

function MyComponent(props) {
  // Using mergeProps to set default values for props
  const finalProps = mergeProps({ defaultName: "Ryan Carniato" }, props);

  return <div>Hello {finalProps.defaultName}</div>;
}

וזו אותה קומפוננטה בריאקט:

function MyComponent({defaultName = "Ryan Carniato"}) {
    return <div>Hello {defaultName}</div>;
}
  1. שימוש ב class במקום className. כן אני יודע כולם כועסים על ריאקט וה className שלהם, אבל תכל'ס אני שמח לכתוב קומפוננטה שמקבלת className בתור פרופ:
function ReactComponent({className}) {
    return (<div className={className}>yay</div>)
}

הכתיב הזה לא עובד אם שם הפרמטר היה class, כי class זו מילה שמורה ב JavaScript.

  1. קבלת ערך מסיגנל דורשת קריאה לפונקציה. עוד משהו שלפני שנתיים לא הפריע לי אבל היום נראה מוזר זה השימוש בסיגנלים דרך קריאה לפונקציה:
const [count, setCount] = createSignal(0);
console.log(count()); 

גם בהשוואה ל vue וגם בהשוואה ל preact הכתיב הזה מסורבל. זו דוגמה מתוך התיעוד של preact לסיגנלים שלהם שעובדים בצורה הרבה יותר טבעית:

import { signal } from "@preact/signals";

const count = signal(0);

// Read a signal’s value by accessing .value:
console.log(count.value);   // 0
  1. אקוסיסטם ופופולריות - לפני שנתיים האקוסיסטם של Solid היתה קטנה אבל היתה איזה תחושה שאולי בעתיד זה יתפוס והיא תגדל. היום כבר ברור שסוליד לא יהיה הדבר הגדול הבא והאקוסיסטם לא יגדל.

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

יותר מדי מהונדס

18/07/2024

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

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

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

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

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

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

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

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

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

היום למדתי: שינוי שם שם מפתח במפה בקלוז'ר

17/07/2024

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

>>> d = {}
>>> d[[1, 2, 3]] = 10
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: unhashable type: 'list'

או ב JavaScript כל מפתח שנבחר יהפוך ל String אוטומטית:

> o = {}
{}
> o[2] = 10
10
> o['2']
10

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

3.1.1 :001 > h = { name: "ynon" }
 => {:name=>"ynon"}
3.1.1 :002 > h[:nickname] = h.delete :name
 => "ynon"
3.1.1 :003 > h
 => {:nickname=>"ynon"}

היה לי מילון עם מפתח בשם name ובשביל להחליף אותו ל nickname היה צריך להוציא את המפתח הישן מהמילון ולשים חדש עם אותו ערך.

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

user=> (clojure.set/rename-keys {:name "ynon"} {:name :nickname})
{:nickname "ynon"}

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

טיפ CSS - אפקט שורות מתחת לטקסט

16/07/2024

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

<div class="border-b border-gray-400">
  Hello World
</div>

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

<div class="border-b border-gray-400 mb-2">
  Line 1
</div>
<div class="border-b border-gray-400 mb-2">
  Line 2
</div>
<div class="border-b border-gray-400 mb-2">
  Line 3
</div>

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

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

<div class="p-4 w-full max-w-md mx-auto">
  <div class="relative">
    <div class="absolute inset-0 bg-[size:100%_2rem] bg-[linear-gradient(#999_1px,transparent_1px)] bg-[position:0_-1px]"></div>
    <p class="relative leading-8 pt-[1px]">
      This text now starts from the first line. It appears as if it's written on the notebook lines, aligning perfectly with each line to create a realistic effect.
    </p>
  </div>
</div>

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

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

<div class="p-4 w-full mx-auto">
  <div class="relative">
    <div class="absolute inset-0 bg-[size:100%_2rem] bg-[linear-gradient(#999_1px,transparent_1px)] bg-[position:0_-1px]"></div>
    <p class="relative leading-8 pt-[1px] w-full h-32 ">
    </p>
  </div>
</div>

נ.ב. בשביל לשחק עם הדוגמאות הכי קל לבקר ב https://play.tailwindcss.com/ ולהדביק שם את קוד הטיילווינד כדי לראות את התוצאה בצד ימין של המסך.

כשהתרגיל המחשבתי עובד נגדך

15/07/2024

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

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

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

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

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

פיתרון Advent Of Code 2023 יום 21

14/07/2024

יום 21 של Advent Of Code הציג חידת מפה ממש חמודה בחלק הראשון שהפכה לכאב ראש בחלק השני (שעליו דילגתי). בואו נראה על מה מדובר ולמה וויתרתי על החלק השני, יחד עם הפיתרון של החלק הראשון בשפת סקאלה.

המשך קריאה

היום למדתי להיזהר מקבצי pyc

13/07/2024

החברים ב JFrog פירסמו השבוע פוסט שתפס אותי בהפתעה: https://jfrog.com/blog/leaked-pypi-secret-token-revealed-in-binary-preventing-suppy-chain-attack/

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

def _fetch_github_file(github_repository="owner/repo", ref="main", access_token=None, filename="Dockerfile"):
    headers = {
        "Accept": "application/vnd.github+json",
        "X-GitHub-Api-Version": "2022-11-28",
        "Authorization": "Bearer 0d6a9bb..."
    }
    if access_token is not None:
        headers['Authorization'] = f'token {access_token}'
        ...

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

def _fetch_github_file(github_repository="owner/repo", ref="main", access_token=None, filename="Dockerfile"):
    headers = {
        "Accept": "application/vnd.github+json",
        "X-GitHub-Api-Version": "2022-11-28",
    }
    if access_token is not None:
        headers['Authorization'] = f'token {access_token}'
        ...

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

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

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