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

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

טעות בתהליכי עבודה

08/01/2021

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

טעויות אנוש קורות: כשמתכנת עושה Deploy ל Production במקום לסביבת הטסט זו טעות אנוש; כשאתה מוחק בטעות את הקובץ package.json במקום את package-lock.json זו טעות אנוש; אפילו כשאתה מריץ את הטסטים בטעות על מכונת הפרודקשן ומוחק את כל בסיס הנתונים זו טעות אנוש. טעות אנוש קורית לבן אדם שיודע מה צריך לעשות ובטעות עשה את הדבר הלא נכון.

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

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

למה זה לא מתרסק (או: בעיות שלא רואים)

07/01/2021

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

for _ in range(100_000):
    fout = open('output.txt', 'w')
    fout.write("hahaha\n")

מאחר ומספר ה File Handlers הפתוחים בלינוקס שלי מוגבל ל 1024, ציפיתי להתרסקות מהירה. היא לא הגיעה.

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

האם זה אומר שכדאי לי להתרגל לכתוב כך קוד? אני לא בטוח. יש תוכניות ששומרות הרבה אוביקטים בזיכרון ושם יכול להיות שפייתון לא ימחק את ה File Handles שיצרנו כי התוכנית עדיין שומרת מצביע אליהם. אולי יותר מטרידה האפשרות שקוד שמופיע במערכת ישתכפל (כי מתכנתים רגילים לעשות copy-paste ממקומות אחרים בקוד) ויגיע למקומות בהם הוא כן יוביל לזליגת קבצים פתוחים.

לקריאה נוספת בנושא שווה לקרוא את הפוסט הבא של ראובן לרנר שגם עשה ניסויים עם מימושים נוספים של פייתון (pypy ו jython): https://lerner.co.il/2015/01/18/dont-use-python-close-files-answer-depends/.

איך נוצר חוב טכני

06/01/2021

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

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

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

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

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

  4. כשאנחנו לא עושים Refactoring ביום יום, אלא משאירים את זה ליום אחד כשיהיה זמן.

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

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

אם הייתי מתחיל היום פרויקט SaaS חדש

05/01/2021

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

חוץ מהבעיה של Scope גדול מדי לפרויקט עצמו קשה לקרוא את הפוסט בלי לחשוב על כמות העבודה שסבסטיאן השקיע בשימוש בטכנולוגיה מודרנית. בין השאר הוא מספר על שלושה חודשים של השקעה בפיתוח Build System ו CI, ובהמשך גם על השימוש ב Kubernetes ואפילו השקעה של 3,000 אירו ב UX/UI. כל זה לפני שיש לקוח משלם ראשון.

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

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

  1. פריימוורק פיתוח ווב כמה שיותר מלא - לדוגמה Rails או Django.

  2. בלי פריימוורק צד לקוח, מקסימום jQuery.

  3. מבנה Monolith, בלי Micro Services.

  4. קונה Theme מוכן מהרשת, מוכן לעשות קצת התאמות.

  5. מעלה קוד לשרת עם rsync.

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

טיפ נאמפיי: החזרת אינדקסים מפונקציה

04/01/2021

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

import numpy as np

arr = np.arange(1, 11) * np.arange(1, 11).reshape(-1, 1)

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

In [6]: arr[0:3,0:3]
Out[6]: 
array([[1, 2, 3],
       [2, 4, 6],
       [3, 6, 9]])

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

def get_index():
    # THIS DOES NOT WORK
    return 0:3,0:3

print(arr[get_index()])

וזה מביא אותנו לטיפ היומי - הפונקציה המתאימה במקרה כזה נקראת slice ואני משתמש בה באופן הבא:

In [11]: def get_index():
    ...:     return (slice(0, 3), slice(0, 3))

In [12]: arr[get_index()]
Out[12]: 
array([[1, 2, 3],
       [2, 4, 6],
       [3, 6, 9]])

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

מה בננה

03/01/2021

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

console.log(('b'+'a'+ +'a'+'a').toLowerCase());

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

...

אז מה קרה כאן?

...

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

console.log(('b' + 'a' + Number('a') + 'a').toLowerCase());

וזה כבר מתחיל להיות הגיוני כי ב JavaScript המרה של המחרוזת a למספר מחזירה את הערך המיוחד NaN שמשמעותו כמו שמו היא פשוט Not A Number (הגיוני, כי a אינו מספר):

> Number('a')
NaN

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

הגיע הזמן להפסיק להשתמש ב Debugger

02/01/2021

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

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

המשך קריאה

לא חשבתי שאפשר

01/01/2021

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

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

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

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

איך לברוח מלולאה כפולה ב Python

31/12/2020

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

for i in values:
    for j in values:
        for k in values:
            if i != j and j != k and i + j + k == 2020:
                print(f"Found! {i}, {j}, {k}")
                break

זה עובד, אבל... כך נראית התוצאה:

Found! 289, 480, 1251
Found! 289, 1251, 480
Found! 480, 289, 1251
Found! 480, 1251, 289
Found! 1251, 289, 480
Found! 1251, 480, 289

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

מה בכל זאת אפשר לעשות? דרך קלה לברוח מלולאה מקוננת היא להפוך אותה ללולאה רגילה באמצעות yield. הקוד נראה כך:

def triplets(values):
    for i in values:
        for j in values:
            for k in values:
                if i != j and j != k:
                    yield(i, j, k)


for i, j, k in triplets(values):
    if i + j + k == 2020:
        print(f"Found! {i}, {j}, {k}")
        break

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

הפונקציה itertools.product היא דרך גנרית לבנות כזה מנגנון ובצורה מקוצרת כך שהקוד הבא עובד מעולה:

import itertools
for i, j, k in itertools.product(values, values, values):
    if i != j != k and i + j + k == 2020:
        print(f"Found! {i}, {j}, {k}")
        break

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

for i, j, k in itertools.combinations(values, 3):
    if i + j + k == 2020:
        print(f"Found! {i}, {j}, {k}")
        break

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

מילים ומשפטים

30/12/2020

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

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

מצד שני ברגע שלמדנו מבנה מסוים אנחנו יכולים להשתמש בו בשפות חדשות ככל שנלמד את המילים בהן. לכן תוכנית שמדפיסה את לוח הכפל תיראה מאוד דומה ב C, perl, ruby, python, JavaScript או כל שפה פרוצדורלית אחרת שתיקחו. ברגע שיש בשפה תמיכה בלולאות for עם משתנה רץ, יהיה לנו קל לבנות בה את לוח הכפל. וברגע שיש בשפה פונקציות אנחנו יכולים לבנות בה את הרקורסיה, גם אם התחביר של הגדרת פונקציה וקריאה לה יהיו שונים בין שפות.

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