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

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

אם ערך חוזר ואף אחד לא מסתכל עליו, הוא עדיין משמיע רעש?

27/04/2024

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

def twice(x): x * 2

ברובי זה לא היה קורה. הפונקציה הזאת ברובי מחזירה בדיוק את מה שרציתי - כלומר את המספר שקיבלה כפול 2:

def twice(x) = x * 2

אבל אז מגיעה הדוגמה הבאה ששוב מזכירה שקיצורי דרך בתכנות זה נחמד אבל תמיד עדיף להבין בדיוק מה קורה מתחת:

def fib_rb(n)
  a = 0
  b = 1
  n.times { a, b = b, a + b }
  a
end

בדוגמה הזאת הבלוק (הקוד שרץ n פעמים מוקף בסוגריים מסולסלים) בעצם עושה את זה:

def fib_rb(n)
  a = 0
  b = 1
  n.times {
    a, b = b, a + b
    return [b, a + b]
  }
  a
end

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

def fib_rb(n)
  a = 0
  b = 1
  n.times { a, b = b, a + b; nil }
  a
end

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

טיפ ריאקט: איך לתפוס לחיצה מחוץ לקומפוננטה

26/04/2024

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

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

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

סך הכל הקוד נראה כך:

import React, { useEffect, useState } from 'react';

const initialText = "Click outside the component to change this text"

export function App(props) {
  const [text, setText] = useState(initialText);

  useEffect(() => {
    const changeText = () => setText('Yay!');
    window.addEventListener('click', changeText);

    return () => {
      window.removeEventListener(changeText);
    }
  }, []);

  return (
    <div
      className='App'
      style={{background: 'red'}}
      onClick={(e) => e.stopPropagation()}
    >
      <h2>{text}</h2>
    </div>
  );
}

מוזמנים לשחק עם הקוד לייב בקודפליי בקישור: https://playcode.io/1848762

טרנזאקציות

25/04/2024

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

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

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

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

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

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

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

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

יום 17 של Advent Of Code 2023 - היום בו סקאלה עבדה נגדי

24/04/2024

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

המשך קריאה

פיתוח קוד שמתאים גם ל Node וגם ל Deno

23/04/2024

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

המשך קריאה

איך כבר הגיע שיעור 4???

22/04/2024

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

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

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

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

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

איך להשתמש בפיתרונות בצורה יעילה?

21/04/2024

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

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

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

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

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

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

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

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

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

לא נראה כמו Ajax

19/04/2024

הביטוי Ajax הוא בכלל קיצור של Asynchronous JavaScript and XML. עכשיו אפשר לשאול, מה קשור XML? אנחנו ב 2024, אבל אני כותב את זה בשביל להזכיר איך דברים משתנים כל הזמן. ה Ajax קשור ל XML כי כשדפדפנים רק התחילו לפנות לשרתים אחרי שעמוד HTML נטען הם השתמשו בממשק שנקרא XMLHttpRequest, שבכלל היה חלק מחבילה של XML (אפילו שדרך הממשק הזה הם קיבלו את התשובה ב JSON). לימים כולם עברו להשתמש בממשק fetch שבכלל לא כולל את המילה XML בשביל לבנות את אותם מנגנונים.

עם גירסה 14 של next.js הקונספט של Ajax שוב משתנה. הפעם מספיק להפעיל await מתוך קוד טיפול באירוע כדי לשלוח הודעה לשרת, לקבל תשובה ולפענח אותה. שימו לב לקוד הבא בצד הלקוח:

async function handleInput(ev: FormEvent<HTMLInputElement>) {
  if (ev.target) {
    const input = ev.target as HTMLInputElement;
    const text = input.value;
    const options = await search(text);
    setOptions(options);
  }
} 

ולקוד שמתאים לו בצד השרת:

export async function search(what: string) {
  if (what.length < 3) {
    return []
  } else {
    return sentences.filter(s => s.toLowerCase().includes(what))
  }
}

יש פה כמה דברים מדהימים:

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

  2. אין צורך לכתוב את הקריאה ל fetch. לא צריך לבנות RTK Query או React Query או שום דבר. הפרמטרים עוברים כמו העברת פרמטרים רגילה ב JavaScript.

האם זה העתיד? לאפליקציות מסוימות בהחלט כן. הבעיה היחידה שעדיין נשארה היא שאין ל next מנגנון טוב לעדכן React Server Components אחרי שינויים בשרת, מה שאומר שאנחנו עדיין צריכים לשמור Client Side State בהרבה אפליקציות. אני מקווה בעתיד הקרוב לראות גם את זה נפתר עם מנגנון Subscriptions אוטומטי ואז נוכל רשמית להיפרד מ Redux וחבריו.

נ.ב. רוצים לראות את הקוד הזה בפעולה? זה הלינק:

https://next-search-demo.vercel.app/

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

https://github.com/ynonp/next-search-demo

טיפ JavaScript - בואו נסתיר מאפיין מ Object.keys

18/04/2024

הפונקציה Object.keys מחזירה את כל המפתחות באובייקט נכון? לא בדיוק. זאת ההגדרה שלה ב MDN:

The Object.keys() static method returns an array of a given object's own enumerable string-keyed property names.

מילת המפתח כאן היא enumerable. היא גורמת ל Keys להחזיר רק את המפתחות שיחזרו מ for ... in, או ליתר דיוק רק את אלה מתוך for ... in שלא הגיעו מהפרוטוטייפ.

הפעלה רגילה עשויה לתת את ההרגשה שזה כל המפתחות:

const object1 = {
  a: 'somestring',
  b: 42,
  c: false,
};

console.log(Object.keys(object1));
// Expected output: Array ["a", "b", "c"]

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

const object1 = {
  a: 'somestring',
  b: 42,
  c: false,
};

Object.defineProperty(object1, 'secret', {
  value: 'secret',
  enumerable: false
})

console.log(Object.keys(object1));
console.log(object1.secret)

הפעם אנחנו עדיין מקבלים רק את המפתחות a, b ו c, למרות שההדפסה השנייה מצליחה ומדפיסה את המילה secret.

ואיך בכל זאת נקבל את כל רשימת המפתחות כולל אלה שאינם enumerable? ב JavaScript חשבו על הכל ויש פונקציה אחרת בשם getOwnPropertyNames שמחזירה את כל המפתחות. הקריאה הזו:

console.log(Object.getOwnPropertyNames(object1))

מדפיסה את:

Array ["a", "b", "c", "secret"]

פונקציית Pipe ושרשור מתודות

17/04/2024

בתיעוד של אמזון אנחנו מוצאים את הדוגמה הבאה לשימוש ב Polly ב Java:

new SynthesizeSpeechRequest()
    .withText(text)
    .withVoiceId(voice.getId())
    .withOutputFormat(format).withEngine("neural");

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

תבנית דומה לה נקראת Fluent Interface והיא מציעה שימוש בשרשור מתודות כדי לתאר פונקציונאליות או רצף פעולות. לדוגמה הקוד הבא מספריית jQuery:

$('#myButton')
  .click(function() {
    $(this).addClass('active');
  })
  .hover(
    function() {
      $(this).css('background-color', 'lightblue');
    },
    function() {
      $(this).css('background-color', '');
    }
  )
  .fadeOut(1000)
  .fadeIn(1000);

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

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

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

function tap(fn) {
  fn(this);

  return this;
};

בואו ניקח דוגמה מ Ruby שם tap כבר מובנית בשפה ונראה איך להשתמש בה כדי להוסיף לולאות לשרשראות של פונקציות. אני מתחיל עם מחלקה בשם Polly שעובדת בתבנית הבנאי עם הקוד הבא:

class Polly
  attr_accessor :text, :engine
  def initialize
    @text = []
  end

  def with_text(text)
    @text.append(text)
    self
  end

  def with_engine(engine)
    @engine = engine
    self
  end

  def print
    puts "Engine: #{@engine}; Text: #{@text}"
  end
end

ועכשיו אני רוצה להפעיל את with_text בלולאה עם המחרוזות a, b ו c. אפשר כמובן להשתמש במשתנה ואז נקבל:

p = Polly.new
p.with_engine("engine")
['a', 'b', 'c'].each {|t| p.with_text(t) }
p.print

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

Polly
    .new
    .with_engine("engine")
    .tap { |p| ['a', 'b', 'c'].reduce(p, &:with_text) }
    .print