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

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

תאימות אחורה

13/12/2020

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

* 20e950e Use Object.create(null) to avoid default object property hazards
* a2c5da8 (tag: v1.3.8) 1.3.8
* af5c6bb Do not use Object.create(null)
* 8b648a1 don't test where our devdeps don't even work
* c74c8af (tag: v1.3.7) 1.3.7
* 024b8b5 update deps, add linting
* 032fbaf Use Object.create(null) to avoid default object property hazards

שימו לב מלמטה למעלה לקומיטים שקשורים ל Object.create:

  1. קומיט 032fbaf עבר להשתמש ב Object.create(null)

  2. קומיט af5c6bb חוזר ל Object.create רגיל

  3. וקומיט 20e950e חוזר ל Object.create(null)

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

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

config.hasOwnProperty(blah)

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

והקומיט האחרון שחוזר ל Object.create(null) ? נו, זאת בדיוק הסיטואציה שבגללה פותחים גירסה 2 של הספריה ושוברים תאימות אחורה.

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

פיתרון מוסבר ל Advent Of Code 2020 יום 5

12/12/2020

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

המשך קריאה

קירות דימיוניים

11/12/2020

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

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

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

למה אתם מתכוונים כשאתם אומרים שלא היה לכם זמן ללמוד השבוע? ועל מה צריך להתגבר כדי שתמצאו את הזמן הזה בשבוע הבא?

במקרה הגרוע ביותר

10/12/2020

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

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

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

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

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

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

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

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

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

איך להפוך את Caps Lock ל Escape ב Linux

09/12/2020

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

המשך קריאה

נהגי מוניות ונגני ג'אז

08/12/2020

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

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

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

השיטה הבטוחה לבנות ביטוי רגולארי לכל טקסט

07/12/2020

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

1-3 l:hello world

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

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

from collections import Counter

def valid(min_count, max_count, char, text):
    min_count = int(min_count)
    max_count = int(max_count)

    return min_count <= Counter(text)[char] <= max_count

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

  1. מתחילים עם הטקסט המקורי

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

  3. נותנים שם לכל חלק

במקרה שלנו מה שיכול להשתנות זה המספרים (תבנית של סיפרה היא [0-9] ואם רוצים כמה ספרות מוסיפים + אחרי), האות (תבנית של אות היא [a-z]) והטקסט שאחרי הנקודותיים (שם יכול להיות כל דבר כלומר .+. לכן הביטוי הרגולארי יהיה:

[0-9]+-[0-9]+ [a-z]:.*

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

(?P<min_count>[0-9]+)-(?P<max_count>[0-9]+) (?P<char>[a-z]):(?P<text>.*)

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

pattern = re.compile(r'(?P<min_count>[0-9]+)-(?P<max_count>[0-9]+) (?P<char>[a-z]):(?P<text>.*)')

if m := pattern.search("1-3 l:hello world"):
    print(valid(**m.groupdict()))
else:
    print("String does not match format")

איפה חותמים?

06/12/2020

אם הבנק היה מציע מסלול חיסכון ב 10% ריבית לשנה אני בטוח שכל אחד היה קופץ לשים שם כמה גרושים. הבעיה עם חיסכון בריבית גבוהה נקראת סיכון ואנחנו הרבה פחות ממהרים לשים את הכסף שלנו במסלול שבו אולי תקבל בסוף 10% לשנה (לא סביר), אולי תקבל 5% ואולי גם תפסיד 50%.

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

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

אבל מה לגבי השקעה בקריירה?

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

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

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

איחוד קומיטים עלול להיות מסוכן

05/12/2020

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

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

class Task < ActiveRecord::Base
    validates :name, presence: true
    validates :description, presence: true, unless: :new_record?
end

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

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

בחזרה ל unless שלנו. בשביל לגלות למה קוד מסוים נכנס למערכת אני משתמש ב git blame:

$ git blame -- app/models/task.rb | grep unless

ומוצא שהשורה נכנסה בקומיט מספר c8fd7f671 עם ההודעה המופלאה:

Squashed commit of the following:

ואחריה רשימה של עשרות קומיטים שאוחדו לקומיט מספר c8fd7f671 הקסום.

בלי ה Squash Commit הצעד הבא שלי היה להפעיל:

$ git log -p c8fd7f671

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

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

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

איך לקבל את הגדרות הטיפוסים של כל קומפוננטת ריאקט ב TypeScript

04/12/2020

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

function InputWithLabel(props: {...}) {
    const { label } = props;
    return (
        <label>{label}</label>
        <input type="text" {...props} />
    );
}

ל TypeScript יש פיתרון נוח למצבים אלה שנקרא React.ComponentProps וזה נראה כך:

type InputProps = React.ComponentProps<'input'>

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

function InputWithLabel(props: { label: string } & InputProps) {
  const { label } = props;
  return (
    <>
      <label>{label}</label>
      <input type="text" {...props} />
    </>
  );
}