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

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

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

מכתב פתוח לתלמיד שנרדם (או: ואם אני צריך עוד זמן)

05/12/2023

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

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

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

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

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

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

הקצב היחיד שעובד הוא הקצב שעובד עבורך.