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

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

זמנים ופרויקטי תוכנה

17/12/2019

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

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

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

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

צריך היה להיות אוטומטי

16/12/2019

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

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

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

מיינדסט של מדריך

15/12/2019

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

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

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

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

בקצב שלי

14/12/2019

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

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

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

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

  2. יש יותר סיכוי שתתאמצו לפתור את החידות של Advent Of Code בחודש דצמבר מאשר אם תנסו לפתוח אותן בפברואר.

  3. יש יותר סיכוי שתצליחו להיכנס לכושר אם תצטרפו לקבוצת ריצה, מאשר אם תנסו לרוץ בקצב שלכם.

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

הרצת קוד אסינכרוני בבדיקות ה pytest שלכם

13/12/2019

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

הספריה pytest-asyncio היא פלאגין ל pytest שמוסיפה אפשרות לסמן בדיקות או Fixtures בתור בדיקות אסינכרוניות ובאופן אוטומטי תפעיל את הבדיקות האלה בתור קורוטינות ותעביר להן את ה event loop.

בשביל לשלב אותה בקוד הבדיקות שלכם מספיק להתקין מ pip ואז לסמן את הבדיקה בתור בדיקה אסינכרונית באופן הבא:

@pytest.mark.asyncio
async def test_luke_name(aiosession):
    data = await fetch(aiosession, 'https://swapi.co/api/people/1/')
    assert data['name'] == 'Luke Skywalker'

בדיקה זו משתמשת בפונקציית עזר בשם fetch (אסינכרונית גם היא) וב Fixture אסינכרוני בשם aiosession. הנה הקוד לשניהם:

@pytest.fixture()
async def aiosession():
    async with aiohttp.ClientSession() as session:
        yield session

async def fetch(session, url):
    async with session.get(url) as response:
        return await response.json()

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

@pytest.fixture(scope='session')
def event_loop():
    loop = asyncio.get_event_loop()
    yield loop
    loop.close()


@pytest.fixture(scope='session')
async def aiosession():
    async with aiohttp.ClientSession() as session:
        yield session

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

איך זה עדיין לא עובד???

11/12/2019

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

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

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

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

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

הדבר הכי גרוע שיכול לקרות לפרויקט עם בדיקות אוטומטיות

10/12/2019

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

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

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

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

הדרך הכי טובה להתעלם מתיקיה או קובץ עם find

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

אז בואו ניזכר רגע בהפעלה פשוטה של find למשל בשביל למצוא קבצים שהשם שלהם הוא package.json:

$ find . -type f -name package.json

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

  1. אנחנו מחפשים קובץ
  2. השם חייב להיות package.json

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

שרשראות תנאים יכולות גם להתפצל, למשל ה find הבא שמשתמש ב or יזהה קבצים בשם package.json או תיקיות בשם node_modules:

$ find . \( -type f -name package.json \) -or \( -type d -name node_modules \)

והנה עלינו לשתי שרשראות: או שעברת את כל השרשרת הראשונה, או שעברת את כל השרשרת השניה. כל שרשרת מורכבת משני תנאים.

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

$ find . -name 'one.txt' -exec ls -l {} \; -type d

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

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

$ find . -type f -not -name 'one.txt' -name 'o*.txt'

ואם רוצים להתעלם מתיקיות הפקודה prune גורמת ל find לסיים את השרשרת ולדלג על התיקיה (היא רלוונטית רק כשהנתיב הוא תיקיה). לכן הפקודה הבאה תחפש את הקובץ package.json בכל מקום מלבד בתוך תיקיית node_modules:

$ find . -type d -name node_modules -prune -false -or -name package.json

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

שקרים לבנים קטנים

08/12/2019

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

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

import produce from "immer"

const byId = produce((draft, action) => {
    switch (action.type) {
        case RECEIVE_PRODUCTS:
            action.products.forEach(product => {
                draft[product.id] = product
            })
            return
    }
})

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

const byId = (state, action) => {
    switch (action.type) {
        case RECEIVE_PRODUCTS:
            return {
                ...state,
                ...action.products.reduce((obj, product) => {
                    obj[product.id] = product
                    return obj
                }, {})
            }
        default:
            return state
    }
}

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

const byId = (state, action) => {
    switch (action.type) {
        case RECEIVE_PRODUCTS:
          const productsData = action.products.reduce((obj, product) => {
            obj[product.id] = product
            return obj
          }, {})
          const immutableProductsData = Immutable.fromJS(productsData);
          return state.merge(immutableProductsData);

        default:
            return state
    }
}

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

אז מה עדיף? כמו תמיד בחיים - זה תלוי:

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

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

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