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

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

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

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

הפוסט שלא כתבתי

20/10/2023

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

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

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

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

לא Java, מחרוזת וביטוי רגולארי זה שני דברים שונים

19/10/2023

ל Java יש API לעבודה עם ביטויים רגולאריים דרך החבילה java.util.Pattern. עם החבילה הזאת אפשר לכתוב דברים כמו:

Pattern p = Pattern.compile("a*b");
Matcher m = p.matcher("aaaaab");
boolean b = m.matches();

או אפילו להשתמש בפונקציות הסטטיות ולכתוב:

boolean b = Pattern.matches("a*b", "aaaaab");

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

מחוץ לחבילת הביטויים הרגולאריים יש חבילה של מחרוזות ובה הפונקציה split:

public String[] split(String regex)

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

import java.util.Arrays;

class Main {
  public static void main(String[] args) {
    // prints: [hello, world]
    System.out.println(Arrays.toString("hello-world".split("-")));

    // prints: []
    System.out.println(Arrays.toString("hello.world".split(".")));
  }
}

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

משתני קונטקסט בסקאלה

18/10/2023

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

(let [agent (ssh-agent {})]
  (let [session (session agent "host-ip" {:strict-host-key-checking :no})]
    (with-connection session
      (let [channel (ssh-sftp session)]
        (with-channel-connection channel
          (sftp channel {} :cd "/remote/path")
          (sftp channel {} :put "/some/file" "filename"))))))

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

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

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

def readUsers(config: ServerConfig): Unit =
  println(s"read users from ${config.ip}")

וכך נראית אותה פונקציה כשהפרמטר הזה יכול לעבור בתור "משתנה סביבה":

def readUsers(using config: ServerConfig): Unit =
  println(s"read users from ${config.ip}")

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

@main def main(): Unit =
  given config: ServerConfig(ip = "12.13.14.15", port = 8080)
  readUsers

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

def readUsers(using config: ServerConfig): Unit =
  println(s"read users from ${config.ip}")

def addUser(username: String, password: String)(using config: ServerConfig): Unit =
  println(s"add user ${username} to ${config.ip}")

def wrapper(using ServerConfig): Unit =
  readUsers

@main def main(): Unit =
  given config: ServerConfig(ip = "12.13.14.15", port = 8080)

  readUsers
  addUser("foo", "bar")
  wrapper

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

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

(def ^:dynamic *config* {:ip "default-ip"})

(defn read-users []
  (println (str "read users from " (:ip *config*))))

;; Usage
(let [custom-config {:ip "custom-ip"}]
  (binding [*config* custom-config]
    (read-users)))

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

האם מפתחים חייבים להפיץ תוכנות מאובטחות?

17/10/2023

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

המשך קריאה