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

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

חידת ריאקט: דברים שאי אפשר לעשות עם Custom Hooks

28/11/2019

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

שימו לב לקוד הבא שמגדיר Custom Hook כדי ליצור באופן אוטומטי אלמנט Input יחד עם שדה בסטייט שמתאים לו:

function useTextField() {
  const [value, setValue] = useState('');

  function Input(props) {
    return <input type="text" value={value} onChange={(e) => setValue(e.target.value)} />
  }

  return [Input, value, setValue];
}

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

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

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

דוגמאות שלא עובדות

27/11/2019

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

>>> import asyncio

>>> async def main():
...     print('hello')
...     await asyncio.sleep(1)
...     print('world')

>>> asyncio.run(main())
hello
world

זה עובד. וזה מדפיס hello ו world בדיוק כמו שהם כתבו שיקרה. וזה גם משתמש בהמון מילים של asyncio כמו await, async ו asyncio.run. ובכל זאת אם לא הייתי יודע מה כל הפקודות האלה עושות לא חושב שהייתי מרוויח הרבה מקריאת הדוגמא כאן.

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

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

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

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

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

טיפ HTML - הגדרת type על כפתור בטופס

26/11/2019

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

<form>
  <label>
    type something:
    <input type="text" name="text" />
  </label>

  <button>Save</button>
</form>

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

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

<form>
  <label>
    type something:
    <input type="text" name="text" />
  </label>

  <button type="button">Save</button>
</form>

גדול עליי

25/11/2019

״לקרוא עכשיו ספר מקצועי מקצה לקצה? קצת גדול עליי״

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

״לכתוב מערכת בדיקות אוטומטית במקום להריץ הכל לבד? רעיון טוב אבל כרגע טיפה גדול עליי״

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

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

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

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

אפשר לדלג על התרגיל?

24/11/2019

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

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

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

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

שימוש חוזר בקוד פייתון באמצעות Decorator

23/11/2019

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

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

set a 10

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

set a 10
set b a

ישמור בתא a את הערך 10 ובתא b את הערך ששמור עכשיו ב a, כלומר גם 10.

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

class MyParser:
    # ...
    def set(self, reg, val):
        ival = self.read_register_or_value(val)
        self.memory[reg] = val

    def sub(self, reg, val):
        ival = self.read_register_or_value(val)
        self.memory[reg] -= ival

    def mul(self, reg, val):
        ival = self.read_register_or_value(val)
        self.memory[reg] *= ival

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

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

דרך אחת להפוך את הקוד לקצת יותר נעים לעין היא להמיר את הקריאה היזומה בקריאה ל Decorator:

class MyParser:
    @read_value_from_register
    def set(self, reg, val):
        self.memory[reg] = val

    @read_value_from_register
    def sub(self, reg, val):
        self.memory[reg] -= val

    @read_value_from_register
    def mul(self, reg, val):
        self.memory[reg] *= val

כל מה שצריך בשביל המעבר למבנה החדש הוא להגדיר את הפונקציה read_value_from_register מחוץ למחלקה בתור Decorator:

def read_value_from_register(f):
    def inner(self, reg, val):
        ival = self.read_register_or_value(val)
        f(self, reg, ival)

    return inner

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

פרילאנסרים וכלכלת החלטורה

21/11/2019

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

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

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

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

ומתוך דה מרקר:

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

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

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

ביטויים רגולאריים ואנאגרמות... אוי ויי

20/11/2019

לעולם לא אשכח את ההתרגשות שלי ביום ה 4/12/2017 כאשר בשעה שבע בבוקר אריק ווסטל פתח את החידה הרביעית של Advent Of Code אותה שנה. הסיבה למסיבה היתה תוכן החידה, שבמבט ראשון נראה לי כמו הזדמנות נפלאה לכתוב ביטויים רגולאריים ולהיכנס לרשימת הפותרים המהירים של התחרות.

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

\b(\w+)\b.*\b\1\b

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

import fileinput, re

count = 0

pat = re.compile(r'\b(\w+)\b.*\b\1\b')

with fileinput.input() as file:
    for line in file:
        if not pat.search(line):
            count += 1

print(count)

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

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

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

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

dad can add

אנחנו נשנה את המשפט ל:

add acn add

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

import fileinput, re

count1 = 0
count2 = 0

pat = re.compile(r'\b(\w+)\b.*\b\1\b')

with fileinput.input() as file:
    for line in file:
        if not pat.search(line):
            count1 += 1

        line2 = re.sub(
                r'\b(\w+)\b',
                lambda m: ''.join(sorted(m.group(1))),
                line)

        if not pat.search(line2):
            count2 += 1

print(count1)
print(count2)

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

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

שעה ביום

19/11/2019

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

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

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

רק ל CTO יש זמן ללמוד טכנולוגיות חדשות כל היום.

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

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