ar
Feedback
ToCode

ToCode

الذهاب إلى القناة على Telegram

טיפים קצרים למתכנתים מאת ינון פרק

إظهار المزيد
1 420
المشتركون
+124 ساعات
+17 أيام
-430 أيام
أرشيف المشاركات
ToCode
1 420
בוא ננסה המשפט שאני הכי אוהב להגיד, והכי לא אוהב לשמוע. לא היית רוצה לשמוע "בוא ננסה" מהרופאה לפני שאתה נכנס לניתוח. לא היית רוצה לשמוע "אולי זה יעבוד" מהאינסטלטור כשיש סתימה בצנרת. אבל כמתכנתים "בוא ננסה" היא האופציה היחידה קדימה. כשהטכנולוגיה מתקדמת יותר מהר מהתיעוד, "בוא ננסה" הוא הסימן שאנחנו פותרים בעיות חדשות, שאנחנו עדיין לא יודעים מה יעבוד, ושאנחנו מוכנים לקחת את הסיכון. בוא ננסה. אולי זה יעבוד.

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

ToCode
1 420
יציאה מלולאה בסקאלה התוכנית הבאה בפייתון קוראת שורות מהמשתמש, מדפיסה כל שורה חזרה ועוצרת כשמישהו כתב את המילה stop:
while True:
    line = input()
    print(line)
    if line == "stop":
        break
בואו ננסה לתרגם אותה לסקאלה. גירסה 1 - מילה במילה ניסיון לתרגום מילולי לסקאלה לא נשמע מסובך, רק צריך למצוא את המקבילה בסקאלה לכל פקודה בפייתון. לולאת while נשארת לולאת while, פקודת input הופכת ל readLine, תנאי נשאר תנאי - אבל מה לגבי ה break?
def v1(): Unit =
  while (true) {
    val line = readLine()
    println(line)
    if (line == "stop") {
      // ???
    }
  }
דווקא הפקודה הכי חשובה בתוכנית לא קיימת בצורה מובנית בסקאלה. גירסה 2 - ה break של סקאלה הגישה של סקאלה ל break היא קצת מוזרה. במקום להוסיף מילה לשפה הם משתמשים במנגנון המובנה של Exception - מי שרוצה לעצור באמצע לולאה זורק Exception, וקוד שעוטף את הלולאה תופס את ה Exception וממשיך בתוכנית. יש קיצורי דרך כמובן אז הקלאס boundary אחראי על עטיפת הלולאה בקוד שתופס Exceptions והפונקציה break היא בסך הכל throw. הקוד נראה ככה:
def v2(): Unit =
  boundary {
    while (true) {
      val line = readLine()
      println(line)
      if (line == "stop") {
        break()
      }
    }
  }
המימוש של break (מובנה בשפה, לא צריך לכתוב לבד) הוא:
  def break[T](value: T)(using label: Label[T]): Nothing =
    throw Break(label, value)
והמימוש של boundary (שוב לא צריך לכתוב לבד, מגיע עם השפה) הוא:
  inline def apply[T](inline body: Label[T] ?=> T): T =
    val local = Label[T]()
    try body(using local)
    catch case ex: Break[T] @unchecked =>
      if ex.label eq local then ex.value
      else throw ex
גירסה 3 - הגישה הפונקציונאלית גירסה קצת פחות מוזרה של אותו קוד תשתמש ב takeWhile בתור תחליף ל while של פייתון. היתרון ב takeWhile הוא שלא צריך break, פשוט מסיימים את האיטרציה עם ערך "שקר" כדי לעצור. זה הקוד:
def v3(): Unit =
  LazyList.from(0).takeWhile(_ => {
    val line = readLine()
    println(line)
    line != "stop"
  }).toList

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

ToCode
1 420
שימו לב: הגדרת משתני סביבה בתחילת פקודה רוב ה Shell-ים תומכים בכתיב של הגדרת משתני סביבה לפני פקודה ובגלל זה אפשר לכתוב:
FOO=10 bash -c 'echo $FOO'
ולקבל על המסך את המספר 10. אבל כשלוקחים את האמת הפשוטה הזאת ל Dockerfile החיים עלולים להסתבך. הרבה פעמים בכתיבת Dockerfile אנחנו משתמשים ב && כדי לשלב כמה פקודות, לדוגמה:
RUN apt-get update && apt-get install cowsay
אבל אם ננסה להוסיף משתנה סביבה בתחילת השורה לדוגמה בשביל למנוע התקנה אינטרקטיבית צפויה לנו הפתעה:
RUN DEBIAN_FRONTEND=noninteractive apt-get update && apt-get install cowsay
משתנה הסביבה רלוונטי רק לפקודה הראשונה ברצף ולא משפיע על הפקודה שאחריה. אפשר להיווכח בזה בקלות אם נחזור לפקודה מתחילת הפוסט:
FOO=10 bash -c 'echo $FOO' && bash -c 'echo $FOO'
הפעם השורה תדפיס 10 רק פעם אחת, למרות שפקודת ההדפסה מופיעה פעמיים. מה עושים? אפשרות אחת היא להוסיף את משתנה הסביבה בכל פקודה שמריצים גם כשמחברים כמה פקודות יחד, כלומר:
RUN DEBIAN_FRONTEND=noninteractive apt-get update && DEBIAN_FRONTEND=noninteractive apt-get install cowsay
אפשרות קצת יותר נוחה היא להוסיף אותו בשורת ENV לפני הרצת הפקודה:
ENV DEBIAN_FRONTEND=noninteractive
RUN apt-get update && apt-get install cowsay

ToCode
1 420
הדף הכי חשוב בתיעוד (שברוב המקרים בכלל לא כתוב) הדף הכי חשוב בתיעוד של ספריה הוא לא ההסבר על הספריה, לא התיאוריה שעומדת בבסיסה ואפילו לא הדף שמתאר את הפיצ'רים המתקדמים של הספריה. כל אלה חשובים ועוזרים אבל מי שיקרא רק אותם עלול למצוא את עצמו בהמשך הדרך עם באגים שמאוד קשה לפתור. הסיבה היא שבעבודה עם ספריה חדשה קשה מאוד לראות את המקרים בהם הספריה לא עובדת או לא עובדת טוב. בדוגמה מעשית לא מזמן בניתי פרויקט עם שרת הפצת וידאו מסוים, רק בשביל לגלות מאוחר מדי שעבור ה Use Case איתו התמודדנו השרת הוסיף Lag משמעותי לוידאו, ובסופו של דבר אי אפשר היה להשתמש בפיתרון בסביבת הפרודקשן. בגלל זה כל כך אהבתי למצוא את דף ה"באיזה מצבים המנגנון שלנו לא יעיל" כששיחקתי עם ספריית Loro. מצד אחד המצבים שמתוארים שם ברורים מאוד, ועדיין קל לראות איך משתמשים חדשים בספריה יפספסו אותם בגלל חשיבה חיובית. וכמובן אם אין לכם מזל (מה שקורה רוב הזמן) ואתם לומדים ספריה חדשה מאפס, אל תוותרו על שיחה נוקבת עם החברים Chat GPT וגוגל כדי לזהות כמה שיותר מצבים בעייתיים לפני שיוצאים לדרך.

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

ToCode
1 420
היום למדתי: פונקציה שמחזירה פונקציה בסקאלה מה שבינתיים אני מאוד אוהב בסקאלה זה תחביר שכולל אינסוף פינוקים קטנים שנראים כמו קסם. וכן ברור לגמרי שהתוצאה היא שפה מאוד לא ידידותית לאנשים מבחוץ. גם זה חלק מהקסם. אחד הטריקים שלמדתי לאחרונה הוא קיצור דרך לפונקציה שמחזירה פונקציה. מה שב JavaScript נראה ככה:
const add = x => y => x + y;
(שגם בשעתו נראה לי כמו קסם אבל היום כבר הגיוני לגמרי). ובפייתון אנחנו כותבים:
def add(x):
    return lambda y: x + y
מקבל בסקאלה קיצור דרך כבר ברמת החתימה של הפונקציה:
def add(x: Int)(y: Int): Int = x + y
ומה שיפה בקיצור הזה הוא דווקא מה שאין בו - בעוד שבפייתון וב JavaScript יש לי מקום להוסיף קוד לפני שאני מחזיר את פוקנציית ההמשך, בסקאלה (עם תחביר הקיצור) זה לא קיים, וככה לא צריך להסתבך ולקבל רעיונות שאחרי זה נתחרט עליהם. וכן סקאלה גם מאפשרת לכתוב את הגירסה היותר מפורשת לאותו קוד:
def add2(x: Int): (y: Int) => Int = { y =>
  x + y
}
אבל זה פשוט פחות יפה.

ToCode
1 420
כשדברים פשוטים דורשים הרבה קוד לפני כמה ימים בפוסט שדיבר על איזה Design Pattern בריאקט נתקלתי בקטע הזה בתוך קומפוננטת ריאקט:
//...
  const [loading, setLoading] = useState<boolean>(false);
  const [data, setData] = useState<Item[] | null>(null);
  const [error, setError] = useState<Error | undefined>(undefined);

  useEffect(() => {
    const fetchData = async () => {
      setLoading(true);

      try {
        const response = await fetch("/api/users");

        if (!response.ok) {
          const error = await response.json();
          throw new Error(\Error: ${error.error || response.status}\);
        }

        const data = await response.json();
        setData(data);
      } catch (e) {
        setError(e as Error);
      } finally {
        setLoading(false);
      }
    };

    fetchData();
  }, []);

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