es
Feedback
ToCode

ToCode

Ir al canal en Telegram

טיפים קצרים למתכנתים מאת ינון פרק

Mostrar más
1 418
Suscriptores
Sin datos24 horas
Sin datos7 días
Sin datos30 días
Archivo de publicaciones
ToCode
1 419
# חזרה לשיגרה - וובינרים כשמגיע החופש הגדול כולם יודעים שיש תאריך סיום שנקרא 1.9 וכשהוא יגיע אנחנו נחזור לשיגרה לפחות עד החגים. היציאה מהשיגרה של הקורונה היתה שונה: פעם ראשונה בהרבה זמן שאנחנו יוצאים מהמסלול בלי תאריך סיום. יכול להיות ששבוע הבא החצי-גן שיש לנו יימשך, ובאותה מידה ייתכן והוא שוב ייסגר לגמרי. ועדיין בתוך כל זה נראה שהחיים חוזרים לשיגרה מהוססת, והגיע הזמן שגם אנחנו נהמר ונחזור לשיגרת הוובינרים שלנו כאן באתר. בהנחה האופטימית שהחצי-גן הולך להימשך, קבעתי הבוקר שני וובינרים לתקופה הקרובה: בחמישי הבא, ה 21.5 ניפגש לדבר על Github Actions ולהרים מערכת CI לאפליקציית ווב שנכתוב. בעזרת החשבון החינמי של גיטהאב נראה איך אנחנו מריצים בצורה אוטומטית בדיקות ומעלים גירסא והכל דרך ה GUI היפה של גיטהאב. שבועיים אחר כך, ב 4.6 אעביר כאן וובינר על Service Worker. זה API מאוד משמעותי ב Web שיש לו תמיכת דפדפנים רחבה ואתרים רבים נעזרים בו כדי להציע תוכן Offline ולשפר ביצועים. אנחנו נראה 2-3 דוגמאות לכתיבה ועבודה עם Service Worker. שני הוובינרים יתקיימו בעשר בבוקר בזום וכל אחד מהם יימשך קצת פחות משעה. דפי ההרשמה לוובינרים כבר מוכנים ואני מקווה לראות אתכם שם: רישום לוובינר Service Worker: https://www.tocode.co.il/workshops/98 רישום לוובינר Github Actions: https://www.tocode.co.il/workshops/97

ToCode
1 419
# החדשנות מתה. ברוך שפטרנו עדיין לא ברור איך הקורונה תשפיע לטווח הרחוק על העסקים שלנו, אבל די בטוח שהיא לא הסיבה שעולם ה Web וה Mobile הגיע לסוג של יציבות. בעולם ה Web הפריימוורקים המרכזיים: React, Vue ו Angular עומדים כולם על קרקע יציבה. ב React אחרי הכניסה של Hooks כל ה API התייצב והאקוסיסטם נבנית לאט לאט סביב קונספט זה. נכון, עוד מעט נראה את Concurrent Mode בפרודקשן, אבל זה רחוק מלהיות מהפכה. אנגולר כבר בגירסא 9 אבל מאז גירסא 2 אין שינויים מהפכניים ו Vue3 כבר זמינה בגירסת בטא וכנראה תישאר איתנו תקופה ארוכה. גם בדפדפנים עצמם מאז Service Worker קשה להיזכר בפיצ'ר חדש ומהפכני ובעולם ה CSS התמיכה ב Flexbox וגריד כבר יציבה ומכסה את רוב הרשת. בעולם ה Mobile מערכת הפעלה חדשה לטלפונים לא תגיע. ריאקט נייטיב עדיין עובד טוב וכנראה מתכנתים ימשיכו לריב עוד זמן רב אם לבחור בו או ב Flutter, אבל זה בכלל לא חשוב. וזה מעולה. כי זה אומר שאם אתם חושבים על עתיד הפיתוח אחרי הקורונה, בעולם ה Web וה Mobile יש לכם יחסית מעט טכנולוגיות ללמוד, ואת הזמן ללמוד אותן כמו שצריך. מי שילמד היום ריאקט, אנגולר או Vue3 (או את שלושתם) יקבל כלים שהולכים להישאר רלוונטים לשוק תקופה די ארוכה. ומי שתתחיל לפתח מערכת חדשה היום בכל אחת מהטכנולוגיות המובילות תקבל בסיכוי גבוה תמיכה טובה ומערכת אופנתית גם בעוד שנה. עושה רושם שהחדשנות עשתה ברקס רציני בתקופה האחרונה. לא עוד פריימוורק חדש כל יומיים ושכתוב מלא של פריימוורקים פעם בשבוע. לא עוד עידכוני גירסא שבורחים מהמפתחים. סוף סוף אפשר לעבוד.

ToCode
1 419
# מיזוג מילונים מקוננים ב Python חבר ניסה למזג מספר מילונים בצורה מקוננת ב Python וקצת הסתבך אז הוא שלח לי את השאלה ואני קפצתי על ההזדמנות לענות כאן בפוסט. אשמח לשמוע גם דעות שלכם על הפיתרון או רעיונות לכיוונים נוספים. ## מה צריך? נרצה לבנות פונקציה שמקבלת שני מילונים ומעתיקה את המילון השני לתוך הראשון, אבל אם יש בשני המילונים מפתח זהה שהערך שלו הוא מילון נוסף, אז הפונקציה תמזג גם את המילונים הפנימיים וכך הלאה לכל עומק שאפשר לדמיין. ביום רגיל היינו חושבים על פיתרון רקורסיבי, אבל בגלל שאנחנו בפייתון ואין אופטימיזציה לרקורסיות זנב כדאי לוותר על זה כדי שהתוכנית באמת תוכל לעבוד על כל עומק מילון ולא תתרסק כשהמחסנית תעלה על גדותיה. דוגמת שימוש בפונקציה יכולה להיראות כך:
d = dict()
update_dict_with_subdicts(d, {"a": 4})
assert d == {"a": 4}

new_dict = {
        "b": {
            "c": {
                "d": 6
                }
            }
        }
update_dict_with_subdicts(d, new_dict)
assert d == {
        "a": 4,
        "b": {
            "c": {
                "d": 6
                }
            }
        }
        
new_dict = {
        "a": 3,
        "b": {
            "f": 7
            }
        }
update_dict_with_subdicts(d, new_dict)
assert d == {
        "a": 3,
        "b": {
            "c": {
                "d": 6
                },
            "f": 7
            }
        }
## פיתרון בשפת Python הדרך הכי קלה להחליף מימוש רקורסיבי במימוש איטרטיבי היא להוסיף מבנה נתונים של מחסנית. ב Python מבנה הנתונים collections.deque יכול לשמש בקלות בתור מחסנית אם נשתמש בפונקציות pop ו append מאחר ושתיהן עובדות על אותו הצד. הפונקציה append מוסיפה איבר לצד הימני של התור, ו pop מוציאה את האיבר הימני ביותר. בגלל שהפונקציה מקבלת שני פרמטרים אני פשוט מחליף כל קריאה רקורסיבית בהוספה של שני הפרמטרים למחסנית, ואת הפונקציה כולה עוטף במבנה של לולאה, כך שכל איטרציה של הלולאה מחליפה "יציאה" מהקריאה הרקורסיבית. מבחינת הקוד אנחנו רוצים לבדוק את סוגי הערכים בשני הצדדים ולחלק את הפונקציה ל-5 מצבים: 1. אם מפתח מסוים נמצא במילון השני אך לא במילון הראשון, פשוט מעתיקים אותו. 2. אם מפתח נמצא בשני המילונים, והערך שלו הוא dict בשניהם - נוסיף קריאה "רקורסיבית" למיזוג שני הערכים. 3. אם מפתח נמצא בשני המילונים, והערך שלו הוא dict רק בשני, עלינו לזרוק את הערך הישן מהראשון, להחליף אותו במילון ריק ואז לטפל כאילו אנחנו במקרה (2). 4. אם מפתח נמצא בשני המילונים אך במילון השני הערך איננו dict, אפשר פשוט להעתיק אותו, כמו במקרה (1). הקוד עצמו לקח לי קצת פחות מ-20 שורות, ואני די בטוח שאפשר לכתוב את כל ה if-ים של האמצע בצורה יותר תמציתית אם מאחדים תנאים:
def update_dict_with_subdicts(dict1, dict2):
    q = collections.deque([(dict1, dict2)])
    while len(q) > 0:
        d1, d2 = q.pop()
        for k in d2.keys():
            if k not in d1:
                d1[k] = d2[k]
            elif type(d1[k]) == dict and type(d2[k]) == dict:
                q.append((d1[k], d2[k]))
            elif type(d1[k]) != dict and type(d2[k]) == dict:
                d1[k] = {}
                q.append((d1[k], d2[k]))
            elif type(d2[k]) != dict:
                d1[k] = d2[k]
            else:
                raise Exception("wtf")
                
    return dict1

ToCode
1 419
# העלאה ל Production כן, כדאי לבדוק כמה שיותר את המערכת בסביבת בדיקות. כן, כדאי להסתכל על הקוד ולחשוב טוב מה יכול להישבר. כן, כדאי שיהיה לכם סט חזק של בדיקות אוטומטיות. אבל - וזה הדבר החשוב, כל הסיפור הזה לא יכול להחליף את תשומת הלב שלכם ביום של העלאה ל Production. כשהמערכת פוגשת את העולם הכל יכול להישבר ואנחנו מגלים המון דברים שמעטים מצליחים לחשוב עליהם בשלב הפיתוח. מה עושים? 1. משתמשים ב Load Balancer כדי להגיש את הגירסא החדשה רק לחלק קטן מהמשתמשים בשבוע הראשון (Haproxy, ACL ועוגיות יהיו חברים טובים שלכם כשמגדירים את זה). 2. שומרים על מנגנון ביטול העלאת גירסא בלחיצת כפתור, כדי שאם בטעות העליתם משהו שבור יהיה קל להתחרט (אני משתמש ב Capistrano, אבל כל כלי Deployment טוב צריך לעבוד כאן). 3. לוקחים בחשבון תיקוני באגים מ Production בלוחות הזמנים של כל סבב (אם הקודם הסתיים בהעלאת גירסא).

ToCode
1 419
# טיפ JavaScript: אנימציה פשוטה עם Velocity.JS ספריית Velocity.JS היא דרך קלה מאוד לגרום לדברים לזוז או לרקד על המסך. היא משתמשת ב requestAnimationFrame, מספקת API סופר פשוט ובגדול פשוט עובדת. מתאים ליישומים קטנים שלא משתמשים בפריימוורק ובכל זאת אתם רוצים שיראו קצת יותר חמודים. הספריה כוללת פונקציית כניסה בשם Velocity שמקבלת כפרמטר ראשון אלמנט ב DOM, פרמטר שני אוביקט שמתאר את האנימציה ופרמטר שלישי כל מיני אפשרויות אנימציה לשליטה בכל הפרמטרים הקטנים. השורה הבאה למשל תגרום לאלמנט מסוים לרקד כמו ג'לי למשך חצי שניה:
Velocity(box, 'jello', { duration: 500 });
והשורה הזו תגרום לו לזוז לנקודה מסוימת על המסך (בתנאי שה position שלו מוגדר ל absolute):
Velocity(box, { left: 100, top: 100 }, { duration: 500 });
בפרמטר השני אפשר לכתוב כל מאפיין CSS בעולם, והשינוי יתבצע באנימציה. וגם יש תמיכה אוטומטית בחיבור אנימציות כך שאם נכתוב את שתי השורות יחד:
Velocity(box, 'jello', { duration: 500 });
Velocity(box, { left: 100, top: 100 }, { duration: 500 });
האלמנט יקדיש חצי שניה לרקוד כמו ג'לי ואחר כך עוד חצי שניה כדי לרוץ לנקודה 100,100 על המסך. בואו נחבר את הכל יחד בדף HTML. בהנחה שיש לנו HTML שנראה כך:
<!DOCTYPE html>
<html>
    <head>
        <meta charset="UTF8" />
        <title>Animate.CSS Demo</title>
        <style>
            #box { width: 100px; height: 100px; background: blue; position: absolute; }
            body { position: relative; height: 100vh; padding: 0; margin: 0; }
        </style>
    </head>

    <body>
        <div id="box"></div>
        <script src="https://cdnjs.cloudflare.com/ajax/libs/velocity/2.0.5/velocity.min.js"></script>
        <script src="https://cdnjs.cloudflare.com/ajax/libs/velocity/2.0.5/velocity.ui.min.js"></script>
        <script src="demo3.js"></script>
    </body>
</html>
אני יכול להוסיף לו את ה JavaScript הבא כדי לגרום לקופסא לרוץ ממקום למקום וגם לרקוד כמו ג'לי כל פעם שלוחצים עליה:
const box = document.querySelector('#box');
function runaway() {
    const width = document.body.clientWidth - 100;
    const x = Math.random() * width;
    const height = document.body.clientHeight - 100;
    const y = Math.random() * height;
    Velocity(box, 'jello', { duration: 500 });
    Velocity(box, { left: x, top: y }, { duration: 500 });
}

runaway();
box.addEventListener('click', runaway);
ויש גם קודפן: <iframe height="300" style="width: 100%;" scrolling="no" title="gOaozvr" src="https://codepen.io/ynonp/embed/gOaozvr?height=300&theme-id=20374&default-tab=js,result" frameborder="no" allowtransparency="true" allowfullscreen="true"> See the Pen <a href='https://codepen.io/ynonp/pen/gOaozvr'>gOaozvr</a> by Ynon Perek (<a href='https://codepen.io/ynonp'>@ynonp</a>) on <a href='https://codepen.io'>CodePen</a>. </iframe>

ToCode
1 419
# מילה אחת ביום אחת החוויות שאנחנו לא ממש יודעים להתמודד איתן היא ללמוד משהו ממש גדול. משהו שכשאתה מתחיל אותו אתה יודע שבמקרה הכי טוב אתה הולך להקדיש כמה שעות ביום בשנתיים הקרובות כדי להצליח לעשות משהו מועיל קטן. כי הבעיה שמאוד קשה להסתכל כל כך רחוק ועוד לשמור על מוטיבציה. תמיד יש בעיות, תמיד יש דברים יותר חשובים וכמובן תמיד יש את התחושה שממילא אני הולך להתייאש מזה מתישהו, אז עדיף לוותר כבר עכשיו. אוניברסיטה או קורס לטווח ארוך עוזרים לנו להתמודד עם הקושי כי הם מאפשרים להוריד מהגב את הדאגה שזה לא יצליח. כשאתה נרשם לתואר שני עם התמחות ב Machine Learning אתה יודע שבעוד שנתיים אתה תהיה במקום שיכול להתחיל לחפש עבודה בתחום. אבל כשאתה מתחיל ללמוד את זה מאפס וצריך לבנות לעצמך את כל התוכנית, פוטנציאל היאוש הוא הרבה יותר גדול. שיטה אחת שעובדת בשבילי כשצריך ללמוד משהו מאוד גדול לתקופת זמן ארוכה היא להגדיר יעד יומי נמוך ממש. למשל לקרוא פיסקה ביום ולהבין אותה, או לכתוב מאמר קצר על משהו קטן שלמדתי. ברור שאם אני לומד רק פיסקה ביום מתוך ספר ענק שצריך לקרוא עוד 10 כמוהו בשביל להבין ML אני לעולם לא אסיים. אבל זה ממילא לא יהיה המצב: כשהיעד היומי נמוך אנחנו שמחים כל פעם שעברנו אותו, וזה עושה חשק להתיישב ולהשקיע יותר זמן, השקעה שמביאה תוצאות יותר טובות. לאורך זמן הניסיון לרוץ ספרינט ולהספיק ללמוד (למשל את כל ריאקט בשבוע) הוא הרסני. אם יש לך ראיון עבודה על ריאקט עוד שבוע הסיכוי להספיק ללמוד הכל בזמן הוא כמעט אפסי. אבל, אם תשימי יעד של לכתוב תוכנית ריאקט קטנה כל יום, אז אחרי כמה ימים כבר יהיה לך חשק לקרוא קצת יותר על התוכנית היומית או לכתוב דווקא שתי תוכניות באותו יום ולאט לאט קצב הלימוד יגדל באופן טבעי.

ToCode
1 419
# טבלאות לוגים באתר של כמה מאות מבקרים מספיק לשמור את כל הלוגים בקובץ טקסט. עם קצת ידע בביטויים רגולאריים ו grep אתם יכולים למצוא את כל מה שצריך, ו logrotate מכסה את כל צרכי התחזוקה שלכם. בעליה לאלפי או עשרות אלפי מבקרים ביום קובץ לוגים כבר לא עוזר. הלוגים רצים הרבה יותר מדי מהר בשביל שזה יהיה יעיל, ואין לכם דרך לחבר כמה הודעות שקשורות לאותו נושא כדי להתמקד על בעיה ולתקן אותה. פה אנחנו כבר צריכים לעבור לנהל את הלוגים בטבלאות בבסיס הנתונים, לחשוב על סכימות נכונות ועל קשר בין לוגים של נושאים שונים וכמובן לזכור למחוק או לזרוק לארכיון לוגים ישנים. עלות הקמה גבוהה יותר, עלות תחזוקה גבוהה יותר אבל גם תמורה טובה יותר. ובמיליונים? שם כבר נצטרך את Elastic Search ואולי Grafana כדי לקבל את המידע מאורגן ובגרפים, לקבל אתרעות כשדברים מתחילים להישבר וכמובן לעקוב אחרי מהלכים מורכבים הנפרסים על פני מספר שרתים. חשוב לראות שכמו בנעליים, אין טעם לבחור מידה גדולה יותר. עבודת התחזוקה שתצטרכו להשקיע בפיתרון לוגים מתוחכם יותר מתקזזת עם החיסכון בזמן שאולי תגיעו אליו במערכת קטנה. במילים אחרות - גם אם Elastic Search נראה כמו חלום, באתר קטן אפשר למצוא תשובות הרבה יותר מהר בדרכים יותר פרימיטיביות.

ToCode
1 419
# פייתון, מספרים ראשוניים וקריאות של קוד מהו קוד קריא? קוד קריא הוא קוד שעוזר לספר את הסיפור של האלגוריתם בצורה נוספת. הוא קוד שכשאנחנו מסתכלים עליו אנחנו מבינים טוב יותר מה המתכנתת רצתה להגיד ואיך היא רצתה להגיד את זה. קוד קריא הוא קוד שמספר סיפור והסיפור הזה הוא האלגוריתם. וקוד לא קריא? ככל שאנחנו מתרחקים מהאלגוריתם ומוסיפים עוד שורות לקוד רק בשביל המחשב, כך הסיפור שלנו הופך פחות קריא או במילים אחרות לכזה שקשה יותר לדבר עליו. אם אני חוזר לקוד אחרי כמה שבועות שלא נגעתי בו ואני מבין בדיוק מה מתחבר למה ולמה - הכל טוב אני מסתכל על קוד קריא. בדרך כלל אנחנו נצטרך יותר מצעד מנטלי אחד בשביל לחזור מהקוד לאלגוריתם ושווה לשים לב שזה בכוונה. קוד קריא לגמרי זה אולי נחמד בתור מאמר בויקיפדיה, אבל הרבה פעמים דווקא כשאנחנו מתרחקים מתיאור האלגוריתם "הנאיבי" או ה"הגיוני" אנחנו מתקדמים בבניית פיתרונות טובים ומדויקים יותר לבעיה. את הדוגמא הבאה פגשתי במקרה כשחיפשתי על מספרים ראשוניים ברשת. מספר ראשוני הוא מספר שמתחלק רק ב-1 ובעצמו, ושיטה קלה למצוא את כל המספרים הראשוניים בתוך מקטע מסוים נקראת הנפה של ארטוסתנס. נתחיל עם הקוד בגירסא הכי נאיבית שלו שהצלחתי לחשוב עליה ותראו אם אתם מצליחים להבין את האלגוריתם:
def find_primes(start, end):
    primes = set(range(start, end))
    k = 2

    while k < end:
        for number in set(primes) - set([k]):
            if (number % k) == 0:
                primes.remove(number)

        k += 1

    return primes
הנה ההסבר שלי בעברית: ניקח את כל המספרים בטווח ואז נמחק מהם את כל הכפולות של 2. אחרי זה מוחקים מהם את כל הכפולות של 3, ואז את כל הכפולות של 4 וכך הלאה עד שמסיימים למחוק את כל הכפולות של כל המספרים. הגיוני? הקוד מתאים לתיאור? אני חושב שכן. הבעיה שהקוד הזה איטי להחריד. לקח למחשב שלי 2 שניות למצוא שישנם 1229 מספרים ראשוניים בין 2 ל-10,000. זה ממש לאט. קל גם לראות את הבעיות במנגנון רק מתוך הקוד (או מתוך התיאור בעברית): 1. לא צריך לבדוק את כל הכפולות של 4. הרי כבר מחקנו את 4 כשמחקנו את 2, ולכן מחקנו גם את כל הכפולות שלו. 2. לא צריך להתקדם עם k עד סוף הטווח, מספיק להתקדם עד שורש של end. 3. לא צריך להתקדם כל פעם ב-1, אפשר לדלג על מספרים זוגיים ולקדם את k ב-2. 4. לא צריך ליצור set חדש כל איטרציה, אפשר למחוק תמיד מאותו set מקורי. 5. הרבה יותר קל לחפש הפוך - במקום לרוץ על כל המספרים ולמחוק את אלה שמתחלקים ב k, אפשר לבנות את כל הכפולות של k ולמחוק רק אותן (פשוט להתקדם בצעדים גדולים יותר). אחרי שחיברתי את כל האופטימיזציות הגעתי לקוד הבא:
def find_primes(start, end):
    k = start if start % 2 == 1 else start + 1
    primes = set(range(start, end)) - set(range(4, end, 2))

    while k < math.sqrt(end) + 1:
        i = 2 * k
        while i < end:
            if i in primes:
                primes.remove(i)
            i += k

        while True:
            k += 2
            if k in primes: break

    return primes
אני אוהב אותו. אולי אפשר לכתוב גירסא יותר קריאה אבל בגדול הוא די ממחיש את האלגוריתם אחרי כל האופטימיזציות וכמובן מבחינת מהירות עם זה כבר אפשר לעבוד. איתור כל 1229 המספרים הראשוניים עד 10,000 לקח 0.04 שניות, וגם כשעליתי לחפש את כל המספרים הראשוניים עד מיליון הצלחתי למצוא את כל 78,498 המספרים בפחות משניה (0.53 שניות למי שרוצה לדייק). אבל- וזו הנקודה החשובה כאן (חוץ מהכיף של מציאת מספרים ראשוניים כמובן) - אם מישהו רוצה להבין את לב האלגוריתם הקוד האיטי משמש נקודת כניסה הרבה יותר טובה. הוא מסביר בדיוק מה אנחנו עושים ומדלג על השטויות שצריך לכתוב בשביל שזה גם יעבוד טוב. מצד שני, הגירסא השניה פחות קריאה כי היא באמת מספרת סיפור הרבה יותר מורכב. יותר מפעם אחת קרה שמצאתי קוד באינטרנט שנראה כמו הגירסא השניה. הוא כלל את כל האופטימיזציות ואת כל הדברים הנכונים כדי שהתוכנית תעבוד, ובגלל זה היה הרבה יותר קשה להבין אותו. מצד שני לא מעט קודים שמצאתי באינטרנט נראו כמו הגירסא הראשונה - הם לא כללו מספיק אופטימיזציות וטיפול במקרי קצה ואיך שתשלבו אותם במערכת אמיתית הם יישברו. ברוב הבעיות שתגיעו לפתור אי אפשר באמת לברוח מלעשות את העבודה הקשה של להבין את הקוד ואז לראות אם הקוד שמצאתם באמת פותר את הבעיה בצורה שגם עובדת במערכת אמיתית.

ToCode
1 419
# דדליינים ומשברי אמון אחת הבעיות עם דדליינים שאפתיים מדי היא יצירת משבר אמון מתמשך בין המתכנתים להנהלה (ראו למשל את הפוסט הזה עם הכותרת "הערכת הזמנים שלכם היא בדיחה"). אחרי פעמיים-שלוש אף אחד לא לוקח ברצינות את הדד ליין שהבוס קבע ואפילו אותו בוס יודע שהמוצר לא יהיה מוכן בזמן שקבע. שיטות עבודה מתקדמות מנסות להתמודד עם זה באמצעות הגדרת "סיפורים" ופגישות בעמידה בתחילת כל יום, וכרטיסיות ולוחות - אבל מעטים הצוותים שמצליחים להגדיר יעד של זמן ולעמוד בו. בדרך כלל אנחנו מגיעים לדדליין עם חצי מהתכולה שרצינו, אחרי שנשארנו המון שעות נוספות כדי להספיק כמה שיותר. הבעיה שעמידה בדדליין דורשת פשרות מכל הארגון: זה לא מספיק שמתכנתת מתאמצת להגיע עם הקוד מוכן בתאריך שנקבע, צריך גם אנשי בדיקות שיעבירו מידע אמין ומדויק על הבעיות, אנשי מוצר שיעדכנו את התכולות תוך כדי תנועה ומנהלים שלא מפחדים להעלות גירסא לא מושלמת ומוכנים לספוג את הצעקות של הלקוחות. ואפשר כמובן גם אחרת - "זה יהיה מוכן כשזה יהיה מוכן" זו עמדה לגיטימית לגמרי אם איכות המוצר חשובה לכם יותר מאשר לשחרר אותו. ללארי וול לקח עשרים שנה לשחרר את perl6, אבל יש הרבה מאוד פרויקטי קוד פתוח שמתאמצים לשחרר גירסא בזמן אבל לא מתאבדים על זה. אף אחד לא נפל מהכסא כש Concurrent Mode של ריאקט לא היה מוכן באמצע 2019 (ועדיין לא מוכן לקראת אמצע 2020). אתם קובעים איך אתם, החברים שלכם והלקוחות שלכם יתיחסו לדדליין הבא שתפרסמו - אם אתם מוכנים להתפשר על איכות המוצר כדי להעלות גירסא בזמן זה מעולה, פרסום הדדליין רק ייצור אמון. אם מצד שני אין לכם כוונה לעשות פשרות כואבות עדיף לפרסם אותו כהערכה ולא כתאריך ספציפי. אף אחד לא נופל מהכסא כש"נראה לי שזה יהיה מוכן עד סוף הרבעון" הופך ל"לא הספקנו ברבעון 2, נקווה שזה יהיה מוכן עד סוף 2020". לעומת זאת כשמפרסמים לו"ז גירסאות עתידיות עם תאריכים כדאי מאוד לעשות מאמץ ופשרות כדי שדברים יקרו כמו שכתבתם.

ToCode
1 419
# תכנות מונחה עצמים ומידע שלא רואים קראתי היום פוסט שכתבתי לפני כמה ימים על מימוש אלגוריתם BFS ב Python וגיליתי באג שלא שמתי לב אליו בכתיבה הראשונה של הקוד, אבל שמאפיין לא מעט מהבעיות בתכנות מונחה עצמים. אני מזכיר שהקוד למימוש BFS שכתבתי נראה במקור כך (זה הקוד עם הבאג):
def bfs(q, callback):
    while len(q) > 0:
        node = q.popleft()

        # Skip this node, we've already visited it
        if hasattr(node, 'visited') and node.visited is True: continue

        # Now we can start processing the node:
        # 1. Let's notify outside code that we found a new node
        callback(node)

        # 2. Let's mark it as visited so we won't have to
        #    process it again
        node.visited = True

        # 3. Let's add all of its neighbors to the queue
        #    so we'll be able to process them too
        for friend in node.neighbours:
            q.append(friend)
זיהיתם את הבאג? אם לא, קחו כמה דקות לקרוא את זה שוב. הבעיה היא המנגנון ששומר האם כבר ביקרנו ב Node מסוים או לא: המנגנון מסמן את ה Node בתור כזה שביקרנו בו כדי שלא נחזור לאותו Node פעמיים, אבל מה קורה אם נרצה להפעיל את bfs פעם שניה על אותו עץ? במצב כזה כל הצמתים כבר מסומנים בתור כאלה שביקרנו בהם ולכן הריצה השניה תחזור מיד. זו בעיה אופיינית לתכנות מונחה עצמים מאחר וכשהאוביקטים עצמם שומרים את המידע, והמידע מפוזר על פני אוביקטים רבים בתוכנית, זו אחריות שלנו לזכור לאפס את המידע כשסיימנו איתו - אחריות שקל לשכוח ממנה. פיתרון אחד קל לבעיה עבור מנגנון ה bfs יהיה להשתמש במבנה נתונים חיצוני ולשמור בו את כל הצמתים בהם ביקרנו. כך נראה הקוד עם קבוצה:
def bfs(q, callback):
    visited_nodes = set()
    while len(q) > 0:
        node = q.popleft()

        # Skip this node, we've already visited it
        if node in visited_nodes: continue

        # Now we can start processing the node:
        # 1. Let's notify outside code that we found a new node
        callback(node)

        # 2. Let's mark it as visited so we won't have to
        #    process it again
        visited_nodes.add(node)

        # 3. Let's add all of its neighbors to the queue
        #    so we'll be able to process them too
        for friend in node.neighbours:
            q.append(friend)
זה גם הפיתרון שעכשיו מופיע בפוסט המקורי כדי לא להשאיר שם טעויות. פיתרון שני שמעניין לראות עושה שימוש ב ExitStack כדי להוסיף קוד ניקוי אחרי כל שינוי, זה נראה כך:
from contextlib import ExitStack
def bfs(q, callback):
    with ExitStack() as stack:
        while len(q) > 0:
            node = q.popleft()

            # Skip this node, we've already visited it
            if hasattr(node, 'visited') and node.visited is True: continue

            # Now we can start processing the node:
            # 1. Let's notify outside code that we found a new node
            callback(node)

            # 2. Let's mark it as visited so we won't have to
            #    process it again
            node.visited = True
            stack.callback(node.reset_visited)

            # 3. Let's add all of its neighbors to the queue
            #    so we'll be able to process them too
            for friend in node.neighbours:
                q.append(friend)
פיתרון זה יותר גנרי ויותר אופייני לגישה מונחית עצמים כיוון שאפשר להוסיף כל Callback Function שרוצים ל Exit Stack, ובסוף בלוק ה with באופן אוטומטי פייתון יריץ את כל קודי הניקוי שלנו. הוא גם יותר מתאים לגישת ה Object Oriented כיוון שכל אוביקט Node אחראי על קוד הניקוי שלו.