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

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

למי קראת בינוני?

05/12/2021

בציוץ שעורר סערה לא קטנה בשעתו לורי ווס כתב את הדברים הבאים:

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

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

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

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

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

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

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

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


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

  1. הוא לומד מטעויות ומשתמש בניסיון לטובתו.

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

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

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

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

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

אם אני צריך לבחור בין אדריכל שממציא בניינים חדשים לבין אדריכל שמראה Track Record של בניינים אמינים שבנה ומצליח לתקשר לי את כל השיקולים והבחירות בבניה - אני בוחר את השני לבנות כל בניין שאני צריך.

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

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


נ.ב. אחרון בגיזרת הקונטקסט. למי שלא מכיר אז הבחור המצייץ הוא Co-Founder של npm (כן מערכת ניהול החבילות ל Node.JS, זאת שמחזיקה את מאגר החבילות הגדול בעולם). הוא מתכנת ווב מאז 1996 ולפני npm הקים חברה בשם awe.sm שנמכרה ל Unified. הוא הרצה ב JS Conf וכנסים נוספים וכותב בלוג טכני מוצלח כבר 20 שנה. יום אחד נצטרך לשאול את עצמנו איך אנשים מעולים מהתעשיה נתקעים עם רגשי נחיתות לא מוצדקים.

לא שמתי לב

03/12/2021

לא שמתי לב לגודל של ה Docker Image ... עד שהתחיל להיגמר לי המקום על הדיסק.

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

לא שמתי לב לאיטיות השאילתות ב DB ... עד שקוד התחיל להיכשל על Timeouts בדיוק בשיא העומס על המערכת.

לא שמתי לב לבעיות האבטחה בקוד ... עד הפריצה שהפילה לי את כל המערכת לשבועות.

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

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

מדריך: תמיכה במספר סביבות קוברנטס עם Kustomize

02/12/2021

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

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

המנגנון המובנה ב kubectl לניהול שינויים בין סביבות נקרא kustomize. בפוסט זה אציג דוגמה פשוטה לפרויקט שמשתמש ב kustomize כדי לייצר קבצי yaml שונים לסביבות השונות.

המשך קריאה

מצאו את ההבדלים

01/12/2021

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

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

(בגיט יש מנגנון מובנה בשם git bisect שבנוי בדיוק על הרעיון הזה)

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

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

חלומות על עננים

30/11/2021

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

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

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

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

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

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

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

  4. אנחנו צריכים דרך לשתף spec שלם של מערכת על ענן. משהו כמו helm chart או docker compose עבור כל אותם אינסוף ממשקים של AWS או Azure או GCP או מה שלא תרצו.

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

ומה אתכם? איזה חלומות יש לכם על הענן?

קיים בשפה

29/11/2021

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

a = [1, 2, 3]
b = ['a', 'b', 'c']
c = ['@', '!', '#']

אז הפעלה של zip על שלושת הרשימות תחזיר רשימה אחת של שלושה איברים שכל אחד מהם מורכב מאיבר אחד מכל אחת מהרשימות, כלומר:

>>> list(zip(a, b, c))
[(1, 'a', '@'), (2, 'b', '!'), (3, 'c', '#')]

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

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

a = [1, 2, 3, 4, 5, 6]
b = ['a', 'b', 'c']
c = ['@', '!', '#']

אז התוצאה של ה zip תישאר בדיוק אותו דבר - והאיברים העודפים ב a פשוט לא יקבלו טיפול.

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

It is clear from the author's personal experience and a survey of the standard library that much (if not most) zip usage involves iterables that must be of equal length.

ובהמשך אותו מסמך:

In fact, the author has counted dozens of other call sites in Python's standard library and tooling where it would be appropriate to enable this new feature immediately.

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

ופה יש שני דברים שצריך לשים לב אליהם:

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

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

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

עיקרון הרצף

27/11/2021

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

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

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

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

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

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

פייתון ומפתחות בלי מרכאות

26/11/2021

באג מעניין בתוכנית שלי הזכיר לי לפני כמה ימים למה חשוב להשתמש ב Type Checking בפייתון. הקוד שלי אחרי ניקוי נראה משהו כזה:

filters = [
        { id: 12, filter: None },
        { id: 15, filter: None },
        { id: 16, filter: lambda x: x > 7 }
        ]

def run_filter(filter_id, data):
    fn = next(x for x in filters if x.get("id") == filter_id)["filter"]
    return [x for x in data if fn(x)]

print(run_filter(16, [2, 5, 9, 11, 20]))

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

Traceback (most recent call last):
  File "/Users/ynonp/tmp/blog/c.py", line 11, in <module>
    print(run_filter(16, [2, 5, 9, 11, 20]))
  File "/Users/ynonp/tmp/blog/c.py", line 8, in run_filter
    fn = next(x for x in filters if x.get("id") == filter_id)["filter"]
StopIteration

שאומרת ש next נכשל כי הוא לא מצא אף פילטר שמתאים לתנאי. זה מוזר כי אני די בטוח שהוגדר פילטר שה id שלו הוא 16. כרגע הקוד לא כולל Type Hints ולכן גם mypy לא מתלונן.

נוסיף Type Hints ונראה אם זה יעזור לקבל יותר מידע:

from collections.abc import Callable
from typing import TypedDict 

class Filter(TypedDict):
    id: int
    filter: Callable[[int], bool]|None

filters: list[Filter] = [
        { id: 12, filter: None },
        { id: 15, filter: None },
        { id: 16, filter: lambda x: x > 7 }
        ]

def run_filter(filter_id, data):
    fn = next(x for x in filters if x.get("id") == filter_id)["filter"]
    return [x for x in data if fn(x)]

print(run_filter(16, [2, 5, 9, 11, 20]))

התוכנית עדיין מתרסקת אבל עכשיו לפחות mypy מצליח למצוא בעיות:

$ mypy b.py

b.py:9: error: Expected TypedDict key to be string literal
b.py:10: error: Expected TypedDict key to be string literal
b.py:11: error: Expected TypedDict key to be string literal
Found 3 errors in 1 file (checked 1 source file)

מבט ממוקד יותר בשורה 9 חושף את הבעיה:

{ id: 12, filter: None },

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

[{<built-in function id>: 12, <class 'filter'>: None}, {<built-in function id>: 15, <class 'filter'>: None}, {<built-in function id>: 16, <class 'filter'>: <function <lambda> at 0x1032b3d90>}]

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

from collections.abc import Callable
from typing import TypedDict 

class Filter(TypedDict):
    id: int
    filter: Callable[[int], bool]|None

filters: list[Filter] = [
        { "id": 12, "filter": None },
        { "id": 15, "filter": None },
        { "id": 16, "filter": lambda x: x > 7 }
        ]

def run_filter(filter_id, data):
    fn = next(x for x in filters if x["id"] == filter_id)["filter"]
    return [x for x in data if fn(x)]

print(run_filter(16, [2, 5, 9, 11, 20]))

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

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