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

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

מדריך Next.JS חלק 6 - שילוב קומפוננטות צד-לקוח וצד-שרת

15/12/2023

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

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

המשך קריאה

מדריך Next.JS חלק 5 - עדכון בסיס הנתונים מטופס בדפדפן

14/12/2023

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

המשך קריאה

מדריך Next.JS חלק 3 - ניווט בין דפים

12/12/2023

אחד האתגרים המשמעותיים בפיתוח Single Page Application ושימוש ב JavaScript Frameworks היה תמיד הניווט בין דפים. בפעולה הרגילה שלו בדפדפן כל פעם שעוברים לדף חדש באתר הדפדפן טוען קובץ HTML חדש ואת כל הנכסים שלו ומציג את התוכן החדש, וכמובן מנקה את כל הזיכרון של ה JavaScript. הבעיה ביישומי Single Page Applications, או אולי יותר נכון להגיד היתרון ביישומים כאלה, הוא שעכשיו אין צורך לטעון את כל הדף מחדש במעבר בין דפים, ושבעזרת JavaScript אפשר לתת למשתמש הרגשה של מעבר בין דפים בלי לעבור דרך מנגנון זה בדפדפן, וכך לקבל ביצועים הרבה יותר טובים.

המשך קריאה

חידת גרמלין - Coalesce

09/12/2023

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

g
  .V()
  .coalesce(
    __.has('person', 'name', 'bob'),
    __.addV('person').property('name', 'bob')
  )
  .iterate()

המשך קריאה

תיקונים קלים, תיקונים קשים ובדיקות

08/12/2023

בפרויקט Node.JS שעבדתי עליו מצאתי את השורה הזאת:

const [host, port] = address.split(':');

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

התיקון הקל הוא בסך הכל:

const [host, port = '8080'] = address.split(':');

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

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

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

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

טיפ סקאלה: החזרת תוצאה עתידית

07/12/2023

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

הפונקציה הבאה מייצרת ומחזירה רשימה של מספרים:

  def createNumbers(): List[Int] =
    (1 to 10).map(_ =>
      Thread.sleep(Random.nextInt(500))
      Random.nextInt(100)).toList

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

createNumbers().foreach(println(_))

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

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

  def createFutureNumbers(): List[Future[Int]] =
    (1 to 10).map(_ => Future[Int] {
      Thread.sleep(Random.nextInt(500))
      Random.nextInt(100)
    }).toList

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

val futures = createFutureNumbers()
futures.foreach(f => f.onComplete(i => println(i.get)))

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

  @main
  def f(): Unit =
      val futures = createFutureNumbers()
      futures.foreach(f => f.onComplete(i => println(i.get)))
      Await.ready(Future.sequence(futures), Duration.Inf)

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

  def f(): Unit =
      val futures = createFutureNumbers().map {f =>
        f.flatMap(i => Future({
          Thread.sleep(i)
          i * i
        }))
      }

      futures.foreach(f => f.onComplete(i => println(i.get)))
      Await.ready(Future.sequence(futures), Duration.Inf)

לא יכול ללמד את זה

06/12/2023

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

# v1 -
def is_prime_v1(n):
    for i in range(2, int(n ** 0.5) + 1):
        if n % i == 0:
            return False
    return True

# v2 -
from math import sqrt
def is_prime_v2(n):
    return not any(n % i == 0 for i in range(2, int(sqrt(n)) + 1))

ההבדל היחיד הוא כמה מושגים צריך להכיר כדי להבין את הקוד. במימוש הראשון מספיק להבין מה זה פונקציה ולהתמודד עם לולאות for ו range. המימוש השני כבר דורש היכרות עם Generator Comprehension, עם הפונקציה any ועם פקודת import.

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