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

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

מסע דילוגים

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 דקות בלבד מרגע הפנייה וככל שידוע לא היתה פעילות חשודה עם טוקן זה.

איך להתחיל לפתח Miniapp לבוט בטלגרם

12/07/2024

אחת היכולות המלהיבות של בוטים בטלגרם היא האפשרות "לברוח" מהבוט לדף ווב, כאשר דף הווב מקבל המון מידע מטלגרם כדי לייצר מראה אחיד בין הבוט לאפליקציה. פירוט מלא על מיניאפס אפשר למצוא בדף התיעוד של טלגרם כאן: https://core.telegram.org/bots/webapps.

ואם כבר יש לכם בוט אלה השלבים בקצרה:

  1. יוצרים תיקייה על המחשב עבור הבוט. אפשר לבנות ווב אפ מתוחכם או בשביל הדוגמה קובץ html פשוט. המינימום שצריך זה קובץ index.html וקובץ manifest.json.

קובץ ה manifest.json נראה ככה:

{
  "name": "My Telegram Mini App",
  "short_name": "Mini App",
  "description": "A brief description of what your Mini App does",
  "version": "1.0.0",
  "start_url": "/index.html",
  "display": "standalone",
  "background_color": "#ffffff",
  "theme_color": "#0088cc",
  "icons": [
    {
      "src": "/icon-192x192.png",
      "sizes": "192x192",
      "type": "image/png"
    },
    {
      "src": "/icon-512x512.png",
      "sizes": "512x512",
      "type": "image/png"
    }
  ]
}

והקובץ index.html נראה ככה:

<!DOCTYPE html>
<html>
<head>
    <link rel="manifest" href="/manifest.json">
</head>
<body>
<script src="https://telegram.org/js/telegram-web-app.js"></script>
<script>
    const webapp = window.Telegram.WebApp;
    webapp.ready();
</script>
</body>
</html>
  1. מתקינים https://telebit.cloud או ngrok או כלי דומה כדי לקבל URL ציבורי לסרביס שרץ אצלכם על המחשב.

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

  3. מפעילים את טלביט לפורט של השרת שיצרתם. בדוגמה שלי אני מפעיל:

python -m http.server

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

~/telebit http 8000

כדי לחבר את הפייתון ל URL הציבורי שקיבלתי מטלביט.

  1. שולחים הודעה ל Botfather עם הפקודה /newapp.

  2. עונים על המון שאלות. השאלה היחידה שחשובה שם היא ה URL של ה Web App. מכניסים שם את ה URL שקיבלנו מטלביט (זה שמחובר לשרת ווב שרץ אצלנו על המחשב).

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

  4. אחרי שמסיימים לפתח תוכלו להעלות את דף הווב לשרת אמיתי ולעדכן את הכתובת באמצעות שליחת הודעת /editapp ל botfather.

שמונה מיומנויות גיט שממש שווה להכיר בעל פה

11/07/2024

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

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

  2. להסתכל איך נראה קובץ בקומיט אחר.

  3. לעבור בין ענפים, מקומיים או מרוחקים.

  4. לראות את רשימת כל השרתים המרוחקים איתם הפרויקט שלכם מסונכרן.

  5. לראות מה היה הקומיט שגרם לשורה מסוימת להצטרף לקוד, או לבלוק מסוים להימחק.

  6. לקבל רשימה של שמות הקבצים שהשתנו בקומיט מסוים.

  7. למחוק ענפים מקומיים או מרוחקים.

  8. לפתור קונפליקטים אחרי merge או cherry-pick או rebase או כל דבר אחר שיוצר אותם.

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