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

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

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

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 הוא שכך יותר קל לתרגם את הקוד גם לשפות אחרות אם יהיה צורך.

למה כל כך מסובך?

06/06/2023

למה אתה מסתבך עם Deployment דרך קוברנטיס? אצלנו פשוט עושים rsync לשרת והכל עובד.

למה את צריכה לכתוב סקריפט שיבצע את השינוי המבני ב DB בצורה מתועדת? אצלנו אני מתחבר ל DB ומריץ את הפקודות מתוך ה phpmyadmin.

למה אתה מסתבך עם Ansible? בעשרים דקות אני מרים לך שרת לינוקס עם כל התוכנות עליו.

מה פתאום Micro Service בענן? פשוט תבנה Dockerfile ותפעיל docker compose.

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


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

חיסכון? לא בטוח

05/06/2023

האימרה המפורסמת של דונלד קנות' הולכת ככה-

“The real problem is that programmers have spent far too much time worrying about efficiency in the wrong places and at the wrong times; premature optimization is the root of all evil (or at least most of it) in programming.”

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

המסר היותר עדין הוא שלפעמים דווקא הניסיון לחסוך עלול לסבך אותנו עוד יותר. לא רק במובן שנעבוד יותר קשה בבניית אותה מערכת, אלא גם שנקבל באגים שבלי אותו ניסיון לא היינו חולמים עליהם. ואת זה אנחנו מכירים: שומרים מידע ב Cache רק בשביל לגלות שהאפליקציה מציגה מידע לא עדכני למשתמש כי שכחנו לרענן את ה Cache; מוסיפים useCallback או useMemo בריאקט וטועים ברשימת התלויות, כך שהקומפוננטה לא מתעדכנת; בונים מערכת שתדע להתמודד עם עומס חריג ולהעלות קיבולת דינמית עם Micro Services ו Kubernetes רק בשביל לגלות שמרוב חלקים נעים אנחנו מאבדים הודעות ב Message Queue.

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

איך להוציא בקשת HTTP ב Clojure

04/06/2023

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

התפיסה של קלוז'ר היא שאלה דברים שאמורים להיות מפותחים בספריות הרחבה או להשתמש ביכולות של השפה המארחת, כלומר ב JVM או בדפדפן.

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

המשך קריאה

האתגר של הטווח הארוך

03/06/2023

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

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

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

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

02/06/2023

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

  redis:
    image: redis
    ports:
      - 6379:6379

אחרי שעתיים הרדיס התרסק ובלוג הופיעה הודעה דומה להודעה הבאה (רק עם תאריך אחר בהתחלה):

1:S 25 Jun 2021 00:48:12.902 * MASTER <-> REPLICA sync started

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

יכולים לראות מה הבעיה האמיתית שם?

המשך קריאה

לימוד מהיר זה אשליה

01/06/2023

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

it really doesn't take much time for a developer staying on top of these advancements in their area of interest to reach a level of familiarity that rivals those with decades of experience.

וגם במודגש-

Staying on top of a few chosen technologies allows anyone to become an expert in them rather quickly.

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

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

מה שמביא אותי לשתי מחשבות בעקבות הפוסט של קנט-

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

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

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

האק בפייתון: תלות מעגלית

31/05/2023

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

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

# File: a.py
import b

def hi(n):
    print("Hello from a")
    if n < 10:
        b.hi(n+1)

hi(0)
# File: b.py

import a

def hi(n):
    print("hello from b")
    if n < 10:
        a.hi(n+1)

אם נשמור את קטעי הקוד בקבצים בשמות a.py ו b.py בהתאמה ונריץ python a.py נקבל את השגיאה:

Hello from a
Traceback (most recent call last):
  File "/Users/ynonp/tmp/python/circular/a.py", line 1, in <module>
    import b
  File "/Users/ynonp/tmp/python/circular/b.py", line 1, in <module>
    import a
  File "/Users/ynonp/tmp/python/circular/a.py", line 8, in <module>
    hi(0)
  File "/Users/ynonp/tmp/python/circular/a.py", line 6, in hi
    b.hi(n+1)
    ^^^^
AttributeError: partially initialized module 'b' has no attribute 'hi' (most likely due to a circular import)

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

import a

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

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

האק מרושע שיכול לעזור במצבים כאלה הוא להעביר את ה import לתוך הפונקציה. אחרי השינוי המודול b.py נראה כך:

# File: b.py

def hi(n):
    import a
    print("hello from b")
    if n < 10:
        a.hi(n+1)

הפעלה חוזרת והכל עכשיו עובד:

Hello from a
Hello from a
hello from b
Hello from a
hello from b
Hello from a
hello from b
Hello from a
hello from b
Hello from a
hello from b
Hello from a
hello from b
Hello from a
hello from b
Hello from a
hello from b
Hello from a
hello from b
Hello from a
hello from b
Hello from a

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