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

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

גיוון הוא כבר לא יעד

14/07/2023

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

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

  1. התחרות קשה במיוחד עבור ג'וניורים או עבור עובדים מרוחקים.

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

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

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

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

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

טיפ neo4j: איחוד צמתים

13/07/2023

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

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

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

MATCH (w:Word)-[:IN_LANGUAGE]-(l:Language)

CALL {
    WITH w
    MATCH (m:Word)-[:IN_LANGUAGE]-(l)
    WHERE m <> w AND m.text = w.text
    RETURN w AS innerWord
}

WITH innerWord.text as text, collect(innerWord) AS nodes
CALL apoc.refactor.mergeNodes(nodes) YIELD node
RETURN node;

בואו נקרא ונתרגם:

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

  2. בתוך בלוק ה CALL מופעלת תת-שאילתה. תת השאילתה לוקחת את w מבחוץ ומחפשת צמתים אחרים של מילים שאינם w אבל הן באותה שפה ויש להן את אותו טקסט.

  3. מחוץ לבלוק ה CALL הפקודה WITH מייצרת אגרגציה ומסדרת את השורות שחוזרות מהשאילתה - בכל שורה יהיה הטקסט של המילה ורשימת כל צמתי המילים שיש להם את הטקסט הזה.

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

סך הכל Cypher (שזו שפת השאילתות של neo4j) יותר פשוט לכתיבה וקריאה מ SQL, וכולל המון פונקציות מובנות כמו collect ו mergeNodes שעוזרות בכתיבת שאילתות.

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

הסיפור סביב "לא סיימתי את כל התרגילים"

12/07/2023

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

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

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

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

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

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

טיפ פייתון: הרצת קוד JavaScript עם js2py

11/07/2023

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

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

var DOCUMENTATION_OPTIONS = {
    URL_ROOT: document.getElementById("documentation_options").getAttribute('data-url_root'),
    VERSION: '1.14.2',
    LANGUAGE: 'en',
    COLLAPSE_INDEX: false,
    BUILDER: 'html',
    FILE_SUFFIX: '.html',
    LINK_SUFFIX: '.html',
    HAS_SOURCE: true,
    SOURCELINK_SUFFIX: '.txt',
    NAVIGATION_WITH_KEYS: false,
    SHOW_SEARCH_SUMMARY: true,
    ENABLE_SEARCH_SHORTCUTS: true,
};

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

code = requests.get('https://dramatiq.io/_static/documentation_options.js').text
ctx = js2py.EvalJs()
ctx.execute("document = {getElementById() { return {getAttribute() { return null }}}}")
ctx.execute(code)
print(ctx.DOCUMENTATION_OPTIONS)

data = ctx.DOCUMENTATION_OPTIONS.to_dict()
print(len(data))

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

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

היו שלום defaultProps על פונקציות

10/07/2023

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

function User({name="Guest"}) {
...
}

ולא ב-

function User(props) {
}

User.defaultProps = {
    name: 'Guest',
};

הכתיב ימשיך להיתמך ב Class Components, ויתווסף לרשימת הדברים ש"צריך ללמוד" כדי להשתמש ב Class Components ולא עובדים ב Function Components.

היו שלום defaultProps. אני אתגעגע.

לא למבחן

09/07/2023

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

מוח שמעדיף לקרוא תשובה מהירה של ChatGPT מאשר את כל ה Man Page.

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

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

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

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

לבנות לעצמך את הכלים

08/07/2023

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

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

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

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

  3. בהיבט קורות החיים קל להוסיף לכלים כאלה דף פרופיל ב Github Pages ולעבות את קורות החיים עם פרויקטים אמיתיים, שאפילו יש להם משתמשים אמיתיים (טוב לפחות משתמש אחד).

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

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

באגים, Best Practices ואינטואיציה

07/07/2023

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

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

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

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

מי מפחד מ Chat GPT בראיונות עבודה

06/07/2023

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

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

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

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

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

היום למדתי: RANDOM ב zsh עלול להיות מבלבל

05/07/2023

ב bash המשתנה המיוחד RANDOM מוחלף במספר אקראי. לכן אפשר לכתוב קוד כזה:

$ echo $0
bash

$ echo $RANDOM
5953
$ echo $RANDOM
12763
$ echo $RANDOM
2894

וזה עובד ממש בסדר. גם בתוך Subshells המשתנה RANDOM ממשיך לעבוד ובגלל זה אפשר לכתוב גם את זה ב bash ולקבל שלוש שורות שונות בקובץ:

$ echo $0
bash

$ echo $RANDOM | tee -a numbers
3064
$ echo $RANDOM | tee -a numbers
4881
$ echo $RANDOM | tee -a numbers
6698

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

$ echo $0
-zsh

$ echo $RANDOM
4402

$ echo $RANDOM
2260

$ echo $RANDOM
12200

אבל הדפסת מספר אקראי בתוך Subshell לא משנה את המצב הפנימי של יצרן המספרים האקראיים ולכן הקוד הבא ידפיס תמיד את אותם שלושה מספרים לקובץ:

$ echo $RANDOM | tee -a numbers
27376
$ echo $RANDOM | tee -a numbers
27376
$ echo $RANDOM | tee -a numbers
27376

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