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

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

הצצה לעתיד: פעולות צד שרת בריאקט ו Next.js 14

30/10/2023

לפני כמה ימים כתבתי כאן על פעולות צד-לקוח, פיצ'ר חמוד של ריאקט שהולך לחסוך לנו כמה הקלקות בכתיבת קוד שמטפל בטפסים. החלק השני שלו, שנקרא Server Actions, כבר הרבה יותר מהפכני ודורש תמיכה מפריימוורק בצד שרת. בזכות Next.JS 14 שיצא עכשיו נוכל לראות איך זה עובד ולהבין את הכיוון של ריאקט בתור Full Stack Framework.

המשך קריאה

היום למדתי: הפלוס הקטן בסקאלה

29/10/2023

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

case class User(name: String)

ואולי קלאס של מסמך:

class Document(title: String, content: String)

בשביל לשמור אותם לשרת מרוחק אני צריך להוסיף לכל אחד מהם url, ובשביל לא לשנות את הקלאסים אני יכול להשתמש ב Generic וליצור גירסה "מרוחקת" שלהם, כלומר:

case class Remote[S](value: S, url: String)

אז עכשיו משהו שהוא Remote[User] יחזיק שדה בשם value מסוג User וגם שדה בשם url מסוג String. ואולי נרצה לכתוב פונקציה כללית שיכולה לקבל משהו שהוא Remote ולשמור את הערך מ value לשרת המרוחק באמצעות HTTP Post. היינו עשויים לחשוב שהחתימה של פונקציה כזו תיראה כך:

def save(what: Remote[Any]): Unit =
  println(s"Saving ...")

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

@main def main()=
  val a = Remote[User](value = User("ynon"), url = "/admin")
  save(a)

לא מצליח להתקמפל בגלל השגיאה:

Found:    (a : Remote[User])
Required: Remote[Any]
  save(a)

מה קרה כאן? הפונקציה מצפה לקבל משהו מטיפוס Remote[Any]. נכון ש User הוא סוג של Any ואם היתה לנו פונקציה שמקבלת Any לא היתה בעיה להעביר לה משהו מסוג User, אבל באופן רגיל Remote[User] איננו משהו מסוג Remote[Any].

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

def save[S](what: Remote[S]): Unit =
  println(s"Saving ...${what.value} to ${what.url}")

והכל מתקמפל ועובד.

הפיתרון השני והוא הסיפור של הפוסט הזה נועד למקרים בהם לא הצלחתם להפוך את הפונקציה לגנרית מכל מיני סיבות או שיש לכם Use Case טיפה שונה, ואתם באמת רוצים להגיד משהו אחר על הטיפוס Remote[S] - הייתם רוצים להגיד שאם יש לכם פונקציה שמצפה לקבל Remote[S] לא משנה מה זה S, ויש לכם משהו אחר נקרא לו T שיורש מ S, אז Remote[T] יירש מ Remote[S]. ובשביל להגיד את זה בסקאלה אפשר להוסיף סימן + לפני ה S בהגדרת הטיפוס הגנרי. זה נראה כך:

case class Remote[+S](value: S, url: String)
case class User(name: String)
class Document(title: String, content: String)

def save(what: Remote[Any]): Unit =
  println(s"Saving ...${what.value} to ${what.url}")

@main def main()=
  val x: Any = User("ynon")
  val a = Remote[User](value = User("ynon"), url = "/admin")
  save(a)

וגם הגירסה הזו מתקמפלת ועובדת. ולינק אחרון לסקרנים שרוצים לקרוא עוד על המנגנון הזה בתיעוד של סקאלה, על המינוס שאפשר לרשום במקום הפלוס, ועל השמות באנגלית של שניהם יהיה תיעוד Variances מהסיור בסקאלה: https://docs.scala-lang.org/tour/variances.html

ריאקט מבט לעתיד - פעולות צד לקוח

28/10/2023

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

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

  1. אפשר יהיה להגדיר לאלמנט form מאפיין action שיהיה פונקציה אסינכרונית.

  2. פונקציה זו תופעל כשמישהו ינסה להגיש את התוכן, ותוכל לעדכן State.

  3. אם הפונקציה זורקת שגיאה השגיאה תיחשב כשגיאת render ותיתפס ב Error Boundary הקרוב.

קוד? בטח הנה הדוגמה:

import { useState } from "react";

export default function Todo() {
  const [name, setName] = useState([]);
  async function copy(formData) {
    console.log(formData);
    setName(formData.get("name"));
  }
  return (
    <>
      <form action={copy}>
        <label>
          Type your name:
          <input name="name" />
        </label>
        <button type="submit">submit</button>
      </form>
      <p>Hello {name}</p>
    </>
  );
}

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

הקוד דורש את גירסה 18.3 של ריאקט שעדיין לא יצאה, אבל אפשר לראות גירסה עובדת שלו עם גירסת הפיתוח שלהם כאן: https://codesandbox.io/s/vibrant-glade-dhrfz7?file=/src/App.js.

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

טיפ קידוד - קודם לטפל בכישלונות

27/10/2023

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

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

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

with open('/etc/passwd') as f:
    ...

ממש באותו רגע אני אשאל - ואיך זה יכול להישבר? מה אם אין לי הרשאות לקובץ? מה open יעשה? ומה אם הקובץ לא קיים, מה open יעשה? ומה אם הנתיב הוא בגלל נתיב לתיקיה, מה open יעשה?

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

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

איך לכתוב לבד דיאלוג מודאלי בריאקט ב 2023

26/10/2023

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

המשך קריאה

הסרטים של טום הנקס

24/10/2023

אחת הדוגמאות בקורס SQL החדש של הארוורד היא השאילתה הבאה שמושכת מבסיס הנתונים את כל הסרטים בהם שיחק טום הנקס:

SELECT "title" FROM "movies"
WHERE "id" IN (
    SELECT "movie_id" FROM "stars"
    WHERE "person_id" = (
        SELECT "id" FROM "people"
        WHERE "name" = 'Tom Hanks'
    )
);

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

MATCH (m:Movie)-[:STAR]-(p:Person{name: 'Tom Hanks'})
RETURN m.title;

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

הצעה ל DSL לשאילתות על גרף בסקאלה וגרמלין

23/10/2023

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

המשך קריאה

ואיך בכל זאת אפשר לקצר זמנים?

22/10/2023

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

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

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

  1. עבודה על בעיות חדשות בלי מספיק ידע על הבעיה או על הכלים. אלה מתכנתי ה PHP שעכשיו צריכים לכתוב אפליקציית Front End בריאקט. כשהכל חדש ההתקדמות איטית יותר והרבה פעמים בכיוון הלא נכון.

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

  3. עבודה על פיצ'רים גדולים מדי עבור המוצר במצבו הנוכחי (וכן ברור לי שמנהלי המוצר שמאפיינים את הפיצ'רים לא מרגישים שאלה פיצ'רים גדולים מדי).

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

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

לימוד לא לינארי

21/10/2023

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

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

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

אז איפה בכל זאת הבעיה? שני אתגרים-

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

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

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