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

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

גנרטור רקורסיבי ב Python

20/03/2021

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

@dataclass
class Node:
    value: int
    children: List['Node']

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

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


root = Node(10, [
    Node(5, [
        Node(99, [
            Node(101, []),
            Node(102, []),
            Node(103, []),
            ]),
        Node(9, [
            Node(25, [])
            ]),
        ]),
    Node(8, [])
    ])

עכשיו אפשר להמשיך ולשאול "מה המתקן הכי שווה בפארק?". קל לראות בעין שהערך של המתקן הכי מוצלח הוא 103. עכשיו נראה איך פייתון היה יכול למצוא את זה:

def dfs(node):
    yield node.value
    for child in node.children:
        yield from dfs(child)

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

print(max(dfs(root)))

עכשיו נסו אתם שני תרגילי הרחבה:

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

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

הקוד המלא שכתבתי כתוכנית אחת למי שרוצה לקחת כבסיס לתרגילי ההרחבה הוא:

from dataclasses import dataclass
from typing import List


@dataclass
class Node:
    value: int
    children: List['Node']


def dfs(node):
    yield node.value
    for child in node.children:
        yield from dfs(child)


root = Node(10, [
    Node(5, [
        Node(99, [
            Node(101, []),
            Node(102, []),
            Node(103, []),
            ]),
        Node(9, [
            Node(25, [])
            ]),
        ]),
    Node(8, [])
    ])

print(max(dfs(root)))
print("---")

for v in dfs(root):
    print(v)

עבודה אמיתית ולימוד

19/03/2021

אם הייתי מתחיל ללמוד היום פיתוח Full Stack והייתי צריך לעבור מאפליקציית Node.JS שכתבתי שעובדת אצלי על המכונה להתקנה של האפליקציה בענן, אין שום סיכוי שהייתי הולך ללמוד איך להקים שרת Linux מאפס. עזבו, אפילו היום שאני יודע להקים לעצמי שרתים, כשאני רואה את הממשקים של AWS או Azure DevOps או אפילו Heroku אני מרגיש טיפש להתעסק עם התקנות בעצמי. בלחיצת כפתור מתוך ה IDE היום אפשר להעביר את כל הקוד שלכם לשרת ברשת ולקבל עליו אינסוף מנגנוני ניטור וניהול.

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

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

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

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

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

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

משימות לימוד מעניינות

18/03/2021

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

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

נניח בשביל הדוגמה שאנחנו רוצים ללמוד React ועברנו את ה Tutorial הבסיסי. עכשיו אני יכול להמשיך לדבר על Data Flow ב React ולהתחיל ליצור משימות לימוד יותר ממוקדות:

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

  2. אני רוצה ללמוד איך קומפוננטה שומרת State יותר מורכב אז אלך לממש את Game Of Life בתור קומפוננטה יחידה.

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

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

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

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

  1. אני רוצה ללמוד איך נראית מערכת Web המשלבת קוד צד-שרת עם קוד צד-לקוח ומעבירה מידע באמצעות REST API; אז אלך לבנות מערכת של דף בודד שממחישה את החיבור הזה בכל מיני טכנולוגיות צד-לקוח וכל מיני טכנולוגיות צד שרת.

  2. אני רוצה ללמוד איך להשתמש ב Client Side Router אז אלך לבנות מערכת המורכבת מ-5 דפים שמעבירים מידע ביניהם, ואבנה אותה 4 פעמים כל פעם בטכנולוגיה אחרת.

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

כיף? תלוי את מי שואלים

17/03/2021

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

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

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

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

אתגרים בפיתוח מערכת מבוססת Micro Services

16/03/2021

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

המשך קריאה

כל מה שתרצה

15/03/2021

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

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

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

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

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

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

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

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

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

למה replaceAll חשובה אחרי הכל

13/03/2021

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

המשך קריאה

הבדיקה הראשונה

12/03/2021

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

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

describe("my test", () => {
    it("just works", () => {
        expect(1).toEqual(1);
    });
});

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

הבדיקה הראשונה במערכת היא המדרגה הראשונה. עכשיו אפשר לעשות Copy/Paste ולהמשיך לכתוב את הבדיקה השניה, השלישית וה-500. ובכל צעד להמשיך לגלות טכניקות בדיקה חדשות.

חידת React: סטייט ופרופס

11/03/2021

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

  1. אם אנחנו במצב צפיה הערך של isEditing יהיה false ונציג אלמנט p עם הערך.

  2. אם אנחנו במצב עריכה הערך של isEditing יהיה true ונציג את הערך בתוך input.

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

function EditableText(props) {
    const { value, setValue } = props;
    const [ isEditing, setIsEditing ] = useState(false);
    const inputRef = useRef(null);

    function startEdit() {
        setIsEditing(true);
    }

    function doneEdit() {
        setValue(inputRef.current.value);
        setIsEditing(false);
    }

    if (isEditing) {
        return (
            <>
                <input type="text" ref={inputRef} defaultValue={value} />
                <button onClick={doneEdit}>Save</button>
            </>
        );
    } else {
        return (
            <>
                <p>{value}</p>
                <button onClick={startEdit}>Edit</button>
            </>
        );
    }
}

ההרפתקנים שביניכם יכולים למצוא את הקוד עובד בקודסנדבוקס בקישור https://codesandbox.io/s/objective-cloud-x4zm7?file=/src/App.js.

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

  const [isEditing, setIsEditing] = useState(!value);

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