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

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

משולש האימה

11/08/2023

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

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

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

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

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

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

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

חמישה דברים שהייתי משנה בקלוז'ר

10/08/2023

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

אני כתבתי פוסט מבוא לקלוז'ר בעבר כאן: https://www.tocode.co.il/blog/2019-11-hello-clojure

וגם העברתי יחד עם חבר וובינר על קלוז'ר שמוקלט כאן: https://www.tocode.co.il/past_workshops/95

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

המשך קריאה

יש לי רעיון מבריק!

09/08/2023

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

״מה הבעיה? רק צריך לרשת מ int ולהוסיף ל __add__ פקודת הדפסה. משהו כזה-

class LoggedAddInt(int):
    def __new__(cls, *args, **kwargs):
        args_without_name = {k: v for k, v in kwargs.items() if k != 'name'}
        return super().__new__(cls, *args, **args_without_name)

    def __init__(self, *args, **kwargs):
        self.name = kwargs.get('name', 'New Value')

    def __add__(self, other):
        res = LoggedAddInt(super().__add__(other), name=self.name)
        print(f"{self.name}: {res}")
        return res


counter = LoggedAddInt(0, name="counter")

def do_something():
    global counter
    counter += 1

do_something()
do_something()
do_something()

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

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

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

class NotifyingCounter:
    def __init__(self):
        self.value = 0

    def inc(self):
        self.value += 1
        print(f"counter: {self.value}")

counter = NotifyingCounter()

def do_something():
    counter.inc()

do_something()
do_something()
do_something()

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

אנשים מתים

08/08/2023

הספר The Art Of Deception היה אחד הטובים והמשפיעים שקראתי. בספר קווין מיטניק מתאר דרך דיאלוגים מפורטים ואמינים איך אנחנו תמיד החוליה החלשה באבטחת מידע. הספר לימד אותי חשדנות בריאה ואת הערך של פרטיות.

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

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

טיפ HTML: גם כפתור הוא קלט

07/08/2023

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

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

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

<form method="POST" action="/news">
  <button  name="like" value="like-value" >Like</button>
  <button  name="dislike" value="dislike-value" >Dislike</button>
</form>

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

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

let likes = 0;

router.post('/news', function(req, res, next) {
  console.log(req.body);

  const {like, dislike} = req.body;

  if (like) {
    likes += 1;
  } else if (dislike) {
    likes -= 1;
  }
  res.render('index', { title: 'Express POST /news', likes});
});

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

הפרויקט שלא פורסם

06/08/2023

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

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

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

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

בואו נפתור יחד את פרויקט אוילר תרגיל 11 בשפת Python

05/08/2023

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

https://projecteuler.net/archives

בתרגיל 11 יש מטריצה של מספרים:

08 02 22 97 38 15 00 40 00 75 04 05 07 78 52 12 50 77 91 08
49 49 99 40 17 81 18 57 60 87 17 40 98 43 69 48 04 56 62 00
81 49 31 73 55 79 14 29 93 71 40 67 53 88 30 03 49 13 36 65
52 70 95 23 04 60 11 42 69 24 68 56 01 32 56 71 37 02 36 91
22 31 16 71 51 67 63 89 41 92 36 54 22 40 40 28 66 33 13 80
24 47 32 60 99 03 45 02 44 75 33 53 78 36 84 20 35 17 12 50
32 98 81 28 64 23 67 10 26 38 40 67 59 54 70 66 18 38 64 70
67 26 20 68 02 62 12 20 95 63 94 39 63 08 40 91 66 49 94 21
24 55 58 05 66 73 99 26 97 17 78 78 96 83 14 88 34 89 63 72
21 36 23 09 75 00 76 44 20 45 35 14 00 61 33 97 34 31 33 95
78 17 53 28 22 75 31 67 15 94 03 80 04 62 16 14 09 53 56 92
16 39 05 42 96 35 31 47 55 58 88 24 00 17 54 24 36 29 85 57
86 56 00 48 35 71 89 07 05 44 44 37 44 60 21 58 51 54 17 58
19 80 81 68 05 94 47 69 28 73 92 13 86 52 17 77 04 89 55 40
04 52 08 83 97 35 99 16 07 97 57 32 16 26 26 79 33 27 98 66
88 36 68 87 57 62 20 72 03 46 33 67 46 55 12 32 63 93 53 69
04 42 16 73 38 25 39 11 24 94 72 18 08 46 29 32 40 62 76 36
20 69 36 41 72 30 23 88 34 62 99 69 82 67 59 85 74 04 36 16
20 73 35 29 78 31 90 01 74 31 49 71 48 86 81 16 23 57 05 54
01 70 54 71 83 51 54 69 16 92 33 48 61 43 52 01 89 19 67 48

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

המשך קריאה

חדש באתר: מיני קורס Type Hints בפייתון

04/08/2023

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

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

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

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

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

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

  3. איך (ולמה) לעבוד עם TypeVar.

  4. איך להגדיר פרוטוקול ומתי להשתמש בו, ואיזה פרוטוקולים כבר מוגדרים בשפה.

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

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

מנויים לאתר יכולים כבר לצפות בתוכן בקישור: https://www.tocode.co.il/boosters/13.

ואם אתם עדיין לא מנויים היום הוא הזדמנות מצוינת להירשם.

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

עד השיעור הבא

03/08/2023

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

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

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

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

כמה סיבות לשפר קוד שעובד

02/08/2023

קוד הבדיקה הבא ב PyTest עובד מעולה (גם אם לא ממש ברור למה הוא טוב כי זו בדיקה מומצאת רק בשביל הפוסט):

from unittest.mock import MagicMock

def test_mock(monkeypatch):
    hello = MagicMock()
    fake_len = MagicMock()
    fake_len.return_value = 8
    hello.__len__ = fake_len

    with monkeypatch.context() as m:
        m.setattr(hello, '__len__', fake_len)
        assert len(hello) == 8
        assert fake_len.called

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

def test_mock(monkeypatch):
    hello = MagicMock()
    hello.__len__ = MagicMock(return_value=8)

    monkeypatch.setattr(hello, '__len__', hello.__len__)
    assert len(hello) == 8
    assert hello.__len__.called

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

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

  1. בשביל לתקן אני צריך להבין מה עושה monkeypatch.context ומתי כן צריך להשתמש בו. רק להבין את זה שווה את המאמץ, בלי קשר לקוד שיתקבל.

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

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

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