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

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

מה עושים כשמצליחים את כל התרגילים?

18/04/2023

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

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

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

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

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

מה עושים אם המשחק קל מדי? עולים לשלב הבא.

צריך הארכה

17/04/2023

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

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

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

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

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

ציונים

16/04/2023

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

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

היום, לפחות בתעשיית ההייטק, התשתית הזאת כמעט לא קיימת.

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

כשבתי ספר ממשיכים לדרג תלמידים לפי ציונים הם מזיקים פעמיים:

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

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

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

קפיצה גדולה מדי

15/04/2023

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

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

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

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

מבוא זריז לספריית spaCy

14/04/2023

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

בואו נראה בקצרה כמה פיצ'רים מדליקים של הספריה.

תחילה התקנה - pip עובד כאן רגיל לגמרי:

$ pip install spacy

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

$ python -m spacy download en_core_web_trf

ובשביל ספרדית אפשר להוריד את:

$ python -m spacy download es_dep_news_trf

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

>>> import spacy
>>> en = spacy.load("en_core_web_trf")

את הפונקציה en שנוצרה אני יכול להפעיל על משפט או קטע טקסט באנגלית כדי לקבל אוביקט spacy.tokens.doc.Doc שמתאים לו. אחרי זה אפשר להשתמש באותו אוביקט כדי לקבל מידע על המילים:

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

  2. מכל טוקן אפשר לקחת את המאפיין pos_ כדי לקבל מה תפקידו במשפט.

  3. המאפיין lemma של טוקן מחזיר את הצורה הבסיסית של הדבר (ללא הטיות).

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

>>> text = 'Inflectional morphology is the process by which a root form of a word is modified by adding prefixes or suffixes that specify its grammatical function but do not change its part-of-speech. We say that a lemma (root form) is inflected (modified/combined) with one or more morphological features to create a surface form.'

>>> doc = en(text)
>>> for token in doc[:10]:
>>>     print(token.text, token.pos_, token.lemma_)

Inflectional ADJ inflectional
morphology NOUN morphology
is AUX be
the DET the
process NOUN process
by ADP by
which PRON which
a DET a
root NOUN root
form NOUN form

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

import spacy
from spacy.matcher import Matcher

nlp = spacy.load("en_core_web_sm")
matcher = Matcher(nlp.vocab)
# Add match ID "HelloWorld" with no callback and one pattern
pattern = [{'POS': 'ADJ', 'OP': '?'},
           {'LEMMA': 'match', 'POS': 'NOUN'},
           {'LEMMA': 'be'}]
matcher.add("match", [pattern])

doc = nlp("""
A match is a tool for starting a fire. Typically, modern matches are made of small wooden sticks or stiff paper. One end is coated with a material that can be ignited by frictional heat generated by striking the match against a suitable surface. Wooden matches are packaged in matchboxes, and paper matches are partially cut into rows and stapled into matchbooks.
""")

matches = matcher(doc)
for match_id, start, end in matches:
    string_id = nlp.vocab.strings[match_id]  # Get string representation
    span = doc[start:end]  # The matched span
    print(match_id, string_id, start, end, span.text)

שימו לב למשתנה pattern: הקוד מחפש רצף של מילים שיכול להתחיל ב Adjective, אחריו המילה match בתור שם עצם (יחיד או רבים) ואחריה הפועל be. פלט התוכנית הוא:

16065818573247886523 match 2 4 match is
16065818573247886523 match 13 16 modern matches are
16065818573247886523 match 14 16 matches are
16065818573247886523 match 49 52 Wooden matches are
16065818573247886523 match 50 52 matches are
16065818573247886523 match 58 60 matches are

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

במחשבה שלישית

13/04/2023

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

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

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

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

$ git -c user.name ynon -c user.email ynon@tocode.co.il commmit -m '...'

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

היום למדתי: פרופילים ב docker compose

12/04/2023

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

version: "3.9"
services:
  backend:
    image: backend

  db:
    image: mysql

ויש לי סקריפט בשם myapp migrate שאני יכול להפעיל רק מתוך האימג' של backend, אז אני יכול להעלות את הסרביסים וכשצריך להפעיל את הסקריפט:

$ docker compose up -d
$ docker compose run backend bash -c "myapp migrate"

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

הייתי מעדיף לכתוב סרביס עבור הסקריפט ב docker-compose.yml שיכלול גם את הפקודה ויאפשר להריץ את הסקריפט עם הפרמטרים שאני צריך והאימג' הרלוונטי:

version: "3.9"
services:
  backend:
    image: backend

  db:
    image: mysql

  migrate:
    image: backend
    command: myapp migrate

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

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

version: "3.9"
services:
  backend:
    image: backend

  db:
    image: mysql

  migrate:
    image: backend
    command: myapp migrate
    profiles: ["tools"]

עכשיו אני יכול להפעיל docker compose up כדי להפעיל את המערכת הרגילה, או docker compose run migrate כדי להפעיל את סקריפט המיגרציה.

ניהול מוטיבציה

11/04/2023

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

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

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

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

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

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

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

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

יבוא מעגלי ו Redux Tolkit

10/04/2023

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

המשך קריאה

טיפ פייתון: שימו לב ל b

09/04/2023

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

>>> import base64
>>> base64.b64decode("bmluamE=")
b'ninja'
>>> base64.b64decode("bW9ua2V5")
b'monkey'
>>> base64.b64decode("bG92ZQ==")
b'love'
>>>

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

import base64
import sys

enc = sys.argv[1]

users = ["monkey", "love", "ninja"]

if base64.b64decode(enc) in users:
    print("welcome")

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

אופציה 1:

import base64
import sys

enc = sys.argv[1]

users = [b"monkey", b"love", b"ninja"]

if base64.b64decode(enc) in users:
    print("welcome")

אופציה 2:

import base64
import sys

enc = sys.argv[1]

users = ["monkey", "love", "ninja"]

if base64.b64decode(enc).decode("utf-8") in users:
    print("welcome")