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

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

שני סוגים של Outsourcing

29/07/2023

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

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

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

ה Regexp הלא נכון

28/07/2023

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

^\s*(.*)\s*$

אני יכול לראות למה זה נראה כמו רעיון טוב - אתה חושב שאתה תופס את הטקסט שבאמצע, ויותר מזה אם ננסה את הביטוי ונדפיס את ה Capture Group הראשון התוצאה "תיראה" נכונה:

$ echo "     hello world    " | perl -nE '/^\s*(.*)\s*$/ && say $1'
hello world

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

$ echo "     hello world    " | perl -nE '/^\s*(.*)\s*$/ && say "[$1]"' 
[hello world    ]

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

$ echo "     hello world    " | perl -nE '/^\s*(.*?)\s*$/ && say "[$1]"'
[hello world]

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

שישה שלבים של שימוש בספריה חיצונית

27/07/2023

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

  1. לעשות מה שב Readme.

  2. לחפש עוד דוגמאות ב API Doc.

  3. להיכנס לקוד הספריה כדי להבין טוב יותר איך דברים עובדים.

  4. לחפש Github Issues רלוונטים כדי לפתור בעיות נקודתיות.

  5. לכתוב קוד שיתממשק עם ה Internals של הספריה (ישתמש בפונקציות פרטיות, יעשה Monkey Patch לחלקים שם).

  6. למזלג, ליצור גירסה שלנו ולשלוח Pull Request.

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

ושוב mypy הציל לי את היום

26/07/2023

שימו לב לקוד הבא בלי Type Hints:

items = [{'a': 10, 'b': 20, '_id': 1},
         {'a': 12, 'b': 31, '_id': 2}]

x = 5 # type: int

def print_item(items, index):
    i = items[index]
    del(i['_id'])
    print(i)


print_item(items, 0)
print_item(items, 1)

רואים את הבאג? הקוד מדפיס את שני המילונים אבל מקלקל את הרשימה. אם נדפיס את items בסוף התוכנית נקבל:

[{'a': 10, 'b': 20}, {'a': 12, 'b': 31}]

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

עכשיו אותו הקוד עם Type Hints:

from typing import Mapping

Items = list[Mapping[str, int]]

items: Items = [{'a': 10, 'b': 20, '_id': 1},
                {'a': 12, 'b': 31, '_id': 2}]

x = 5 # type: int

def print_item(items: Items, index: int):
    i = items[index]
    del(i['_id'])
    print(i)


print_item(items, 0)
print_item(items, 1)

print(items)

הפעם כבר בתוך PyCharm ובטח בהפעלת mypy מקבלים את השגיאה:

demo.py:12: error: "Mapping[str, int]" has no attribute "__delitem__"; maybe "__getitem__"?  [attr-defined]
Found 1 error in 1 file (checked 1 source file)

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

הזדמנויות / דברים יותר דחופים

25/07/2023

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

סדר עדיפויות לעתים נדירות משאיר מקום להזדמנויות.

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

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

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

ובבקשה תשתמשו ב Chat GPT בתרגילים הבאים

24/07/2023

עכשיו כש Chat GPT הוא חלק מהחיים המקצועיים שלנו. ועכשיו כשאנחנו משתמשים ונשתמש בו בעבודה כל יום, בדיוק כמו בגוגל.

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

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

משפט המפתח - בבקשה תיעזרו ב Chat GPT במהלך העבודה על התרגילים הבאים.

טיפ פייתון: ערך אקראי מתוך Literal

23/07/2023

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

וכך הטיפ של היום מתחיל בקוד הבא:

from typing import Literal
CountryCode = Literal["il", "al", "ar", "fr"]

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

from typing import Literal
import random
CountryCode = Literal["il", "al", "ar", "fr"]

def get_country_name(code: CountryCode):
    pass

random_country_code = random.choice(CountryCode.__args__)

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

הסוד לצאת מהמינוס

22/07/2023

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

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

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

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

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

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

ואולי ChatGPT זה כמו מחשבון?

21/07/2023

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

אולי המתכנתת שנעזרת ב Chat GPT היא כמו המתמטיקאית שנעזרת במחשבון? אולי מתכנתים צריכים לדעת רק את ה"מה" ולתת למחשב להבין לבד את ה"איך"? ואולי כך יראה המקצוע שלנו בעתיד?

אולי.

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

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

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

מה זה בכלל send של Generator בפייתון?

20/07/2023

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

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

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

from collections.abc import Generator
from itertools import islice

def fib() -> Generator[int, None, None]:
    a, b = 1, 1
    while True:
        yield a
        a, b = b, a + b

for i in islice(fib(), 10):
    print(i)

לא צריך הרבה בשביל להבין שה int הראשון הוא הטיפוס של הדבר שיוצא מ yield, אבל מאיפה הגיעו שני ה None-ים שאחריו?

אז נכון יש Generators שצריכים את send ו throw ובטח יש סיבות טכניות טובות למה לא להשתמש ב Any כברירת מחדל שם. אבל אין ספק שהיה יותר קל להסביר גנרטורים לאנשים אם הייתי יכול קודם ללמד את yield עם ה Type Hint שלו ורק אחר כך ללמד את send ו throw עם הטיפוסים שלהן.

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