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

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

חיסכון? לא בטוח

05/06/2023

האימרה המפורסמת של דונלד קנות' הולכת ככה-

“The real problem is that programmers have spent far too much time worrying about efficiency in the wrong places and at the wrong times; premature optimization is the root of all evil (or at least most of it) in programming.”

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

המסר היותר עדין הוא שלפעמים דווקא הניסיון לחסוך עלול לסבך אותנו עוד יותר. לא רק במובן שנעבוד יותר קשה בבניית אותה מערכת, אלא גם שנקבל באגים שבלי אותו ניסיון לא היינו חולמים עליהם. ואת זה אנחנו מכירים: שומרים מידע ב Cache רק בשביל לגלות שהאפליקציה מציגה מידע לא עדכני למשתמש כי שכחנו לרענן את ה Cache; מוסיפים useCallback או useMemo בריאקט וטועים ברשימת התלויות, כך שהקומפוננטה לא מתעדכנת; בונים מערכת שתדע להתמודד עם עומס חריג ולהעלות קיבולת דינמית עם Micro Services ו Kubernetes רק בשביל לגלות שמרוב חלקים נעים אנחנו מאבדים הודעות ב Message Queue.

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

איך להוציא בקשת HTTP ב Clojure

04/06/2023

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

התפיסה של קלוז'ר היא שאלה דברים שאמורים להיות מפותחים בספריות הרחבה או להשתמש ביכולות של השפה המארחת, כלומר ב JVM או בדפדפן.

לכן אפשר לתרגם את השאלה איך להוציא בקשת HTTP בקלוז'ר לשאלות המקבילות בשפות המארחות - "איך להוציא בקשת HTTP ב Java", ו"איך להוציא בקשת HTTP ב JavaScript מתוך דפדפן". בואו נראה את התשובות ואיך הן מתורגמות לקלוז'ר.

המשך קריאה

האתגר של הטווח הארוך

03/06/2023

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

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

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

הבאג הבא בדוקר קומפוז כמעט עבר לי מתחת לרדאר

02/06/2023

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

  redis:
    image: redis
    ports:
      - 6379:6379

אחרי שעתיים הרדיס התרסק ובלוג הופיעה הודעה דומה להודעה הבאה (רק עם תאריך אחר בהתחלה):

1:S 25 Jun 2021 00:48:12.902 * MASTER <-> REPLICA sync started

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

יכולים לראות מה הבעיה האמיתית שם?

המשך קריאה

לימוד מהיר זה אשליה

01/06/2023

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

it really doesn't take much time for a developer staying on top of these advancements in their area of interest to reach a level of familiarity that rivals those with decades of experience.

וגם במודגש-

Staying on top of a few chosen technologies allows anyone to become an expert in them rather quickly.

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

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

מה שמביא אותי לשתי מחשבות בעקבות הפוסט של קנט-

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

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

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

האק בפייתון: תלות מעגלית

31/05/2023

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

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

# File: a.py
import b

def hi(n):
    print("Hello from a")
    if n < 10:
        b.hi(n+1)

hi(0)
# File: b.py

import a

def hi(n):
    print("hello from b")
    if n < 10:
        a.hi(n+1)

אם נשמור את קטעי הקוד בקבצים בשמות a.py ו b.py בהתאמה ונריץ python a.py נקבל את השגיאה:

Hello from a
Traceback (most recent call last):
  File "/Users/ynonp/tmp/python/circular/a.py", line 1, in <module>
    import b
  File "/Users/ynonp/tmp/python/circular/b.py", line 1, in <module>
    import a
  File "/Users/ynonp/tmp/python/circular/a.py", line 8, in <module>
    hi(0)
  File "/Users/ynonp/tmp/python/circular/a.py", line 6, in hi
    b.hi(n+1)
    ^^^^
AttributeError: partially initialized module 'b' has no attribute 'hi' (most likely due to a circular import)

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

import a

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

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

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

# File: b.py

def hi(n):
    import a
    print("hello from b")
    if n < 10:
        a.hi(n+1)

הפעלה חוזרת והכל עכשיו עובד:

Hello from a
Hello from a
hello from b
Hello from a
hello from b
Hello from a
hello from b
Hello from a
hello from b
Hello from a
hello from b
Hello from a
hello from b
Hello from a
hello from b
Hello from a
hello from b
Hello from a
hello from b
Hello from a
hello from b
Hello from a

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

בעיות מסובכות מדי

30/05/2023

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

ללמוד פיתוח ווב היא לא מאלה. גם ללמוד פייתון. או כל שפת תכנות אחרת.

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

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

זה לא קל, אבל לגמרי אפשרי והסיכויים לטובתכם.

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

לקוף יש בעיה

29/05/2023

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

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

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

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

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

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

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

והנה זה שוב

28/05/2023

הקטע הבא מועתק מתוך התיעוד של next.js:

import { cookies } from 'next/headers';

export default function AddToCart({ productId }) {
  async function addItem(data) {
    'use server';

    const cartId = cookies().get('cartId')?.value;
    await saveToDb({ cartId, data });
  }

  return (
    <form action={addItem}>
      <button type="submit">Add to Cart</button>
    </form>
  );
}

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

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

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

לצייר בתוך הקווים

27/05/2023

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

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

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

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

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

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

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

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