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

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

שילוב React Router Data API עם RTK Query

16/06/2023

אחד השילובים האהובים עליי בתקופה האחרונה הוא החיבור בין Data API של React Router ל RTK Query. בקצרה, ה Data API אומר שאנחנו יכולים להוציא את כל הלוגיקה של "מתי" מידע נטען מהשרת ל Router במקום שיישמר בקומפוננטות, ו RTK Query יודע למדל טוב את הלוגיקה של "איך" מידע נטען מהשרת. יחד הם שילוב מנצח כפי שנראה בפוסט הבא.

המשך קריאה

לחפש את ה"לא אפשרי"

15/06/2023

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

לכל טכנולוגיה יש מגבלות, והבנה של הדברים שאותם אי אפשר לעשות עוזרת לנו להבין טוב יותר מה כן אפשר. זה הכל Trade Offs.

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

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

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

ובחזרה ל ES Modules - בדיוק בגלל שכל ה export-ים חייבים להיות זמינים בזמן קומפילציה דפדפנים יכולים לטעון מהר את התלויות גם בלי להריץ את הקוד. זה חלק מהכח של ES Modules שלא קיים ב CommonJS.

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

איך להתמודד עם FormData מתוך קוד Express

14/06/2023

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

המשך קריאה

צרות של ריאקטיביים

13/06/2023

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

עכשיו הבעיה שלהם הפכה להיות איך לזהות ש r/atom כזה השתנה, והתשובה היא ריקטיביות. בדומה ל Vue ועוד יותר ממנו ל Solid, גם ריאייג'נט מסתכל בזמן חישוב הקומפוננטה לאיזה אטומים ניגשו, ומחבר אותם לקומפוננטה כך ששינוי בערך האטום יגרום לחישוב מחדש של הקומפוננטה.

לכן הקוד הבא ל Counter עובד:

(defn counter []
  ;; run on init, skip on re-calculations
  (r/with-let [value (r/atom 0)]
    [:div
     {:style {:margin "10px"}}
     [:p (str "Value " @value)]
     [:button {:on-click #(swap! value inc)} "+"]
     [:button {:on-click #(swap! value dec)} "-"]]))

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

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

(defn textboxes []
  (r/with-let [value (r/atom "hello")]
    [:div
     (for [i (range 5)]
       [:input {:value @value
                :on-change (fn [ev] (reset! value (.. ev -target -value)))}])]))

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

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

התיקון הוא פשוט אחרי שמבינים את הבעיה - רק צריך להפוך את ה for מלולאה עצלה ללולאה מלאה עם doall והכל מסתדר:

(defn textboxes []
  (r/with-let [value (r/atom "hello")]
    [:div
     (doall
      (for [i (range 5)]
        [:input {:value @value
                 :on-change (fn [ev] (reset! value (.. ev -target -value)))}]))]))

הזמן הנכון להינעל על פיתרון

12/06/2023

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

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

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

מתי אם כן כדאי להפסיק להסתכל הצידה ו"להינעל" על פיתרון? מה צריך לוודא לפני שהולכים All In בכתיבת פיצ'ר בדרך מסוימת? זאת הרשימה שלי, מוזמנים להוסיף את שלכם בתגובות-

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

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

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

  4. צריך להבין את הבעיה ולהשתכנע שהפיתרון המוצע באמת פותר אותה.

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

זה מעולה, אבל...

11/06/2023

זה מעולה. שווה גם לנסות למצוא אלגוריתם יותר יעיל.

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

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

זה מעולה. שווה גם לנסות לכתוב בדיקות אוטומטיות.

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

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

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

לא נשמע כמו עוד משתנה State

10/06/2023

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

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

המשך קריאה

רק לסובב את הפדאלים

09/06/2023

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

אבל רק בגלל שמבחוץ קשה לראות את העבודה האמיתית לא אומר שהיא לא שם.

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

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

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

המטרה היא לא למצוא את התשובה הכי מהר, אלא למצוא את התשובה הנכונה.

עכשיו לומדים? יש עוד באג בפרודקשן

08/06/2023

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

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

״מה את כל היום במחשב? אולי עדיף למצוא עבודה במשהו אחר״

״מה קורה עם הגירסה? שוב נצטרך לדחות את הדדליין?״

״התגלה עוד באג בפרודקשן! עכשיו זה לא זמן ללמוד״

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

כמה מחשבות שאולי יעזרו לעשות סדר-

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

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

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

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

הנקודותיים שלפני הטוקן

07/06/2023

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

ב Azure Devops API טוקן כזה כבר לא מונפק ב OAuth אלא בשיטת אימות ישנה יותר שנקראת Basic Auth. ב Basic Auth אנחנו צריכים להעביר שם משתמש וסיסמה בתוך ה Authorization Header של הבקשה, והטוקן משמש בתור הסיסמה. את שם המשתמש משאירים ריק.

כל הסיפור הזה הוא הקדמה לתשובה ב Stack Overflow שמסבירה איך לדבר עם ה API הזה מפייתון:

import requests
import base64

pat = 'tcd******************************tnq'
authorization = str(base64.b64encode(bytes(':'+pat, 'ascii')), 'ascii')

headers = {
    'Accept': 'application/json',
    'Authorization': 'Basic '+authorization
}

response = requests.get(
    url="https://dev.azure.com/jack0503/_apis/projects?api-version=5.1", headers=headers)
print(response.text)

הקוד לא ארוך אבל משאיר שאלה גדולה פתוחה - למה הם הוסיפו נקודותיים לפני הטוקן?

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

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

import requests
import base64

def basic_authorization_header(username: str, password: str):
    return 'Basic '+str(base64.b64encode(bytes(username + ':'+pat, 'ascii')), 'ascii')

# Personal Access Token received from the web UI
pat = 'tcd******************************tnq'

headers = {
    'Accept': 'application/json',
    'Authorization': basic_authorization_header(username='', password=pat)
}

response = requests.get(
    url="https://dev.azure.com/jack0503/_apis/projects?api-version=5.1", headers=headers)
print(response.text)

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