ToCode
Открыть в Telegram
1 419
Подписчики
Нет данных24 часа
Нет данных7 дней
Нет данных30 день
Архив постов
1 419
# איפה חותמים?
אם הבנק היה מציע מסלול חיסכון ב 10% ריבית לשנה אני בטוח שכל אחד היה קופץ לשים שם כמה גרושים. הבעיה עם חיסכון בריבית גבוהה נקראת סיכון ואנחנו הרבה פחות ממהרים לשים את הכסף שלנו במסלול שבו אולי תקבל בסוף 10% לשנה (לא סביר), אולי תקבל 5% ואולי גם תפסיד 50%.
למרות משחק הסיכון אנחנו גם יודעים שמי שמחזיק את כל הכסף בעובר ושב לטווח הרחוק יפסיד בגלל שכל שנה הממשלה מדפיסה עוד ועוד כסף וכך ערך הכסף תמיד יורד - או בניסוח מקביל שקל יותר לראות אותו - המחירים כל הזמן עולים.
כשזה נוגע לכסף קל לראות שאנחנו צריכים לחלק את ההשקעה ולשים חלק מהסכום במסלול עם הסיכון הנמוך שייתן תשואה נמוכה, וחלק מהסכום במסלול בסיכון גבוה כדי להגדיל את התשואה לאורך זמן. נכון, המסלול בסיכון גבוה לפעמים יעלה ולפעמים ירד, אבל בטווח הרחוק שם נראה את התוצאות הטובות ביותר.
אבל מה לגבי השקעה בקריירה?
אם הייתי מציע לך להשקיע שעה ביום בלימוד טכנולוגיות חדשות, ומבטיח שאחרי חצי שנה אתה תמצא עבודה במשכורת כפולה ממה שאתה מרוויח היום ובאווירה טובה בהרבה, אני בטוח שכל אחד היה חותם. אבל בחיים האמיתיים אף אחד לא יכול להבטיח דבר כזה.
האם בגלל זה שווה לוותר על השקעה בקריירה ולימוד טכנולוגיות חדשות? אין סיכוי. כשאתה מתקדם רק במסלול הקריירה הסולידי אתה מפסיד כי הטכנולוגיה תמיד רצה קדימה. בשביל הקפיצות הגדולות צריך לקחת סיכונים, גם אם חלק מהם יתגלו כהפסד.
המטבע שלנו הוא זמן והחלוקה שלו צריכה להתאים לחלוקה של כספים אצל אנשים שמנהלים השקעות: חלק מהזמן יוקדש למסלול הסולידי (עבודה) וחלק חייב להיות מוקדש למסלול המסוכן - לימוד והתנסות עם טכנולוגיות חדשות. כשאתה משקיע שעה ביום ללמוד טכנולוגיה חדשה אתה מעלה את הסיכוי להיות במקום הנכון כשאחת הטכנולוגיות תביא איתה הזדמנות לזינוק בקריירה.
1 419
# איחוד קומיטים עלול להיות מסוכן
יש משהו יפה בבראנץ שסיימת לעבוד עליו, אתה מכווץ את הכל לקומיט אחד ומחבר ל 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, וכמו שציפיתי השורה הזאת היתה חלק מפיתרון לבעיה גדולה יותר (פיתרון עם באגים, אבל בכל זאת פיתרון). רק דרך שאר השורות מצאתי מה היתה הבעיה הגדולה יותר ויכולתי לתקן את הבאג בלי לשבור את הפיצ'ר שבשבילו הקוד היה שם.
לקח? זה קל - קומיט, כולל כל השינויים וכולל הודעת הקומיט, הוא מכתב שאתם שולחים לאני העתידי שלכם, מכתב שמספר סיפור על בעיה שהתמודדתם איתה, על המחשבות שלכם אל מול אותה בעיה ועל הפיתרון בכללותו שבניתם. אם הרזולוציה גבוהה או נמוכה מדי יהיה קשה לאני העתידי שלכם לקרוא את הסיפור ולהשתמש בו. היו ממוקדים ושימרו על הקומיטים שלכם ממוקדים והאני העתידי שלכם יוכל לישון הרבה יותר טוב.1 419
# איך לקבל את הגדרות הטיפוסים של כל קומפוננטת ריאקט ב TypeScript
בעבודה עם 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} />
</>
);
}1 419
# להבין או לפתור
אם יש לך את הבחירה, עכשיו, היום, עם הבאג הזה שאת תקועה עליו כבר שבועיים - אם יש לך את הבחירה לפתור אותו כמו במטה קסמים, למצוא את השורה הזאת שתתקן הכל. אם את יכולה לקבל את השורה הזאת, לא תקחי?
הבחירה בין להבין או לפתור מתחילה להיות מעניינת רק כשיש התנגשות בין השניים. ברור שאני רוצה גם להבין, אבל נראה אתכם אומרים לבוס שהשורה ההיא מ Stack Overflow שפותרת הכל היא לא מספיק טובה כי אני לא מבינה אותה. ונראה מה הבוס שלכם יגיד.
ועדיין כשמסתכלים קדימה על הקריירה שלך ועל איכות הקוד במערכת קל לראות שההתעקשות לכתוב רק קוד שאני מבין היא תמיד הבחירה המשתלמת. אולי לא משתלמת מבחינת Product (אין לי מושג מה לוחות הזמנים שלהם) אלא הכי משתלמת לעתיד הקריירה שלך.
1 419
אז איזה פונקציה תרוץ יותר מהר? אין לי מושג, וזה גם לא כל כך משנה לחישוב הסיבוכיות. מה שכן חשוב הוא שאם הקלט שלכם הולך להיות ארוך משמעותית בפרודקשן בהשוואה לסביבת הבדיקות, יש סיכוי טוב שהפונקציה השניה תוביל לפחות משתמשים כועסים.
1 419
# חמש דקות על סיבוכיות
אחד השידרוגים שחלמתי עליו הרבה זמן לאתר הזה היה צ'ט בין הגולשים, אתם יודעים, כמו שיש בפייסבוק. לא רציתי איזה אינטרקום או משהו שתוכלו לשאול אותי שאלות, אלא דמיינתי המון תלמידים שואלים ועונים אחד לשני וסתם מתיידדים. ישבתי לכתוב את הקוד על מכונת הפיתוח שלי ושברתי את הראש איך לעצב את הפיצ'ר ואיזה מאפייני HTML אפשר יהיה לרשום בהודעות ואפילו העליתי שרת בדיקות וקשקשתי בצ'ט עם כמה חברים כדי לראות שהכל עובד.
וזה עבד, לפחות עד שעליתי לפרודקשן.
כי מה אני אגיד לכם החיים בפרודקשן הם לא החיים על המחשב המקומי ולשמחתי יש פה הרבה יותר גולשים באתר מחברים שקשקשתי איתם בצ'ט על שרת הבדיקות. כבר בשעות הראשונות אחרי העלאת הפיצ'ר אנשים התחילו לשלוח הודעות "לא מצליח להתחבר לאתר", "לא רואים את הקורסים", ו"מה קרה שהכל נהיה איטי פתאום?". מסתבר שמנגנון הצ'ט המתוחכם שכתבתי כלל קוד שחיפש את כל ההודעות שצריך להציג למשתמש מסוים, וככל שהיו יותר משתמשים באתר הקוד הזה הפך יותר ויותר איטי בצורה אקספוננציאלית.
אם רק הייתי יודע על סיבוכיות כשכתבתי את הקוד.
סיבוכיות היא בדיוק הרעיון הזה שעוזר לנו להסתכל על אלגוריתם ולשאול את השאלה הפשוטה "איך הדבר הזה יתנהג ככל שהקלט יגדל?". הסיבוכיות של אלגוריתם לא אומרת כמה זמן ייקח לו לרוץ, כי יש עוד המון גורמים שמשפיעים על זמני הריצה בעולם האמיתי; אבל היא יכולה לתת מדד איכות טוב לקוד שלנו ולפעמים יכולה להזהיר אותנו מאסון לפני שהוא מגיע למשתמשים.
אנחנו בדרך כלל מסמנים סיבוכיות של אלגוריתם בכתיב O (זו האות O גדולה), אחריה סוגריים ובתוכם פונקציה עם האות n. בטוח ראיתם את הכתיב הזה והנה כמה דוגמאות:
O(n)
O(n ^ 2)
O(logn)
O(n * n)
ה n בתוך הנוסחה מייצג את גודל הקלט ושווה לשים לב שמה שמעניין אותנו ברוב חישובי הסיבוכיות הוא כמה עוד עבודה האלגוריתם יצטרך לבצע ככל שהקלט יגדל בצורה משמעותית. לכן מבחינת סיבוכיות אין הבדל בין 2n, 3n ואפילו 100n - בכל המקרים הסיבוכיות נקראת לינארית ובגדול זה אומר שככל שהקלט יגדל האלגוריתם יצטרך לעבוד קשה יותר אבל בצורה פרופורציונאלית לגודל הקלט.
איך מחשבים סיבוכיות? אני שמח ששאלתם ואנסה לענות דרך דוגמה. באתגר Advent Of Code של אתמול (התחלנו את האתגרים של 2020, אז תתכוננו לחפירות AoC בחודש הקרוב) המשימה היתה למצוא זוגות של מספרים ברשימה שסכומם הוא 2020. לדוגמה ברשימה הזאת:
1721
979
366
299
675
1456
המספרים 1721 ו 299 מתאימים כיוון ש 1721 ועוד 299 נותן 2020.
דרך אחת למצוא את המספרים האלה הוא לרוץ על הקלט בלולאה כפולה:
def find_pair(data, requested_sum):
return [(a, b) for a in data for b in data if a + b == requested_sum]
numbers = [1721, 979, 366, 299, 675, 1456]
print(find_pair(numbers, 2020))
זה עובד ומדפיס את התוצאה הנכונה. כשמסתכלים על כמות הפעולות ועל איך המנגנון יתפקד ככל שהקלט יגדל אנחנו רואים שעל כל מספר שנוסף לקלט על המחשב לבדוק n אפשרויות נוספות, כלומר עלינו לבדוק את המספר החדש אל מול כל המספרים הישנים. סיבוכיות כזאת נקראת O(n*n). אנחנו יכולים לשפר את המצב אם נברח מהלולאה ברגע שמצאנו את ההתאמה הראשונה, אבל במקרה הגרוע ביותר (כשאין זוג מתאים) אנחנו עדיין נצטרך לסיים את כל הלולאה הכפולה כדי להשתכנע שאין זוגות מתאימים.
גישה שניה לפתור את אותה בעיה היא הגישה הלינארית. בגישה זו נשמור את כל המספרים בתוך מבנה נתונים שהחיפוש בו הוא מאוד מהיר (בפייתון יש לנו קבוצה או מילון לצורך זה). כשאני אומר מאוד מהיר אני מתכוון שהחיפוש במבנה הנתונים לא תלוי בכמה דברים קיימים כבר במבנה הנתונים, או שלוקח אותו הזמן לחפש מספר בקבוצה של 3 איברים כמו בקבוצה של 10,000 איברים. לכן אנחנו יכולים לכתוב את האלגוריתם הבא:
def find_pair(data, requested_sum):
dataset = set(data)
for number in data:
if (2020 - number) in dataset:
return (number, 2020 - number)
numbers = [1721, 979, 366, 299, 675, 1456]
print(find_pair(numbers, 2020))
שבמבט ראשון לא נראה שונה משמעותית מבחינת אורך קוד או המורכבות שלו, אבל כן מכיל הבדל מהותי: אם נוסיף איבר לרשימת המספרים שלנו נצטרך לבצע רק פעולה אחת יותר ממה שעשינו קודם. כלומר צריך לבדוק רק אם 2020 פחות המספר החדש קיים בקבוצה dataset, ואנחנו כבר יודעים לעשות את זה בלי קשר לכמה איברים יש בקבוצה.1 419
עכשיו כל מה שצריך בשביל לבטל קיצור מסוים הוא לבחור עבורו את צירוף המקשים undef - במקרה של ה Ctrl+\ שלנו אקליד במסוף או בסקריפט:
$ stty quit undef
ועכשיו אפשר ללחוץ כמה שרוצים על Ctrl+\ בלי ששום תוכנית תקבל סיגנל שלא התכוונתי לשלוח.1 419
# סוד התוכניות הנעלמות
בפעם הראשונה ש mutt סגרה את עצמה חשבתי שזה קורה, שזה באג, שככה זה תוכניות - עובדות עובדות ואז נעלמות. אבל כשזה המשיך לקרות שוב ושוב ועם תוכניות נוספות כבר היה ברור שאי אפשר לתת למחשב להשתולל ככה. מה קרה אתם שואלים? בואו נצא למסע לארץ הסיגנלים בעקבות התוכניות הנעלמות.
## דמו? כן ברור
יש לכם לינוקס? יופי. יש מסוף? מעולה. יש פייתון? גדול. עכשיו כתבו את הסקריפט הבא בקובץ
demo.py והריצו מהמסוף החביב עליכם:
while True:
pass
אני מריץ עם:
$ python demo.py
וכמובן שהסקריפט לא עושה הרבה רק נכנס ללולאה אינסופית. עכשיו לחצו Ctrl+C (מחזיקים את ה Ctrl ואז לוחצים c). נסגר? נסגר.
בואו ננסה את זה שוב הפעם עם כפתור אחר. אותו סקריפט ואותה הרצה:
$ python demo.py
ועכשיו לחצו על Ctrl+\ (מחזיקים את ה ctrl ואז לוחצים על Backslash). נסגר? נסגר.
למה הסקריפט נסגר כל פעם? ומה ההבדל בין שני צירופי המקשים? בואו נדבר רגע על סיגנלים.
## סיגנלים ביוניקס
סיגנל הוא דרך להעביר הודעה בין תוכניות. הפקודה kill מקבלת מזהה תהליך ומזהה סיגנל ושולחת את הסיגנל לתהליך, מערכת ההפעלה לפעמים תחליט לשלוח סיגנלים לתהליכים מסוימים ואנחנו יכולים דרך המקלדת לשלוח כמה סיגנלים לפי מה שמוגדר אצלנו במסוף.
אני חוזר לפייתון ובשביל הדמו הבא נצטרך שני חלונות מסוף (או אחד מפוצל). כתבו את התוכן הבא בקובץ demo.py והריצו:
import os
print(os.getpid())
while True:
pass
בהרצה הפעם נקבל הדפסה של מספר לפני הכניסה ללולאה. המספר נקרא מזהה התהליך הנוכחי ובעזרתו נוכל לשלוח סיגנלים לאותו תהליך. המספר שהודפס אצלי הוא 545878 אז אני נכנס לחלון חדש וכותב:
$ kill -1 545878
ובחלון המקורי של תוכנית הפייתון התוכנית תצא ותופיע ההודעה:
[1] 545878 hangup python demo.py
אתם יכולים לנסות את זה עם מספרים שונים אחרי ה kill, כל מספר מייצג סיגנל אחר. אפשר גם לראות את כל הסיגנלים בעזרת הפקודה kill -l ואצלי התוצאה נראית כך:
$ kill -l
HUP INT QUIT ILL TRAP ABRT BUS FPE KILL USR1 SEGV USR2 PIPE ALRM TERM STKFLT CHLD CONT STOP TSTP TTIN TTOU URG XCPU XFSZ VTALRM PROF WINCH POLL PWR SYS
מה עושים עם הסיגנלים? מסתבר שתוכניות יכולות לבחור לטפל בהם בכל מיני צורות. בפייתון המודול signal מאפשר לנו לתפוס סיגנלים לפי סוג הסיגנל. עדכנו את קוד הדמו לגירסה הבאה:
import os, signal
print(os.getpid())
def hahaha(sig, frame):
print("Hahaha")
signal.signal(signal.SIGINT, hahaha)
while True:
pass
נריץ ונלחץ על Ctrl+C הפעם המחשב כבר לא מוותר וכך נראה הפלט אצלי:
$ python demo.py
547128
^CHahaha
^CHahaha
^CHahaha
קוד התוכנית מטפל בסיגנל שנקרא SIGINT באמצעות הדפסת הטקסט hahaha. סיגנל זה הוא בדיוק מה שמתקבל כשאנחנו לוחצים על Ctrl+C ולכן התוכנית לא יצאה כשלחצנו על צירוף מקשים זה. עדיין אנחנו יכולים לסיים את התוכנית עם Ctrl+\ ששולח את הסיגנל SIGQUIT. אני אשאיר לכם את הכיף לטפל גם ב SIGQUIT ואז לנסות למצוא איך לצאת מהתוכנית.
## אז מה קרה לתוכניות המסוף שלי?
בעבודה עם אימקס הצירוף Ctrl+\ הוא זה שמחליף שפה ובשלב מסוים התרגלתי ללחוץ על צירוף מקשים זה כדי (לנסות) להחליף שפה גם מחוץ לאימקס. התוצאה כמו שאני מקווה שכבר הבנתם היא שליחת סיגנל SIGQUIT ליישום שבמסוף מה שגורם לסגירת התוכנית.
הפיתרון במצבים כאלה הוא ממש פשוט ולא דורש להתרגל לצירופי מקשים חדשים. הפקודה stty של המסוף קובעת איזה כפתור ישלח את הסיגנלים של השונים של המסוף (ועוד קיצורי מקשים מעניינים). אתם יכולים לראות את כל הקיצורים שמוגדרים אצלכם עם:
$ stty -a
speed 38400 baud; rows 23; columns 94; line = 0;
intr = ^C; quit = ^\; erase = ^?; kill = ^U; eof = ^D; eol = <undef>; eol2 = <undef>;
swtch = <undef>; start = ^Q; stop = ^S; susp = ^Z; rprnt = ^R; werase = ^W; lnext = ^V;
discard = ^O; min = 1; time = 0;
-parenb -parodd -cmspar cs8 -hupcl -cstopb cread -clocal -crtscts
-ignbrk -brkint -ignpar -parmrk -inpck -istrip -inlcr -igncr icrnl ixon -ixoff -iuclc -ixany
-imaxbel -iutf8
opost -olcuc -ocrnl onlcr -onocr -onlret -ofill -ofdel nl0 cr0 tab0 bs0 vt0 ff0
isig icanon iexten echo echoe echok -echonl -noflsh -xcase -tostop -echoprt echoctl echoke
-flusho -extproc1 419
# ירח דבש
גם עם טכנולוגיה יש ירח דבש ואתם יכולים למצוא אותו בהרבה מאוד Tutorials ברשת: בן אדם מראה לך איך לכתוב מערכת בלוג ב 15 דקות, אבל לא יספר לך מילה על מה הולך לקרות חצי שנה לתוך הפרויקט.
התקופה הזאת של Green Field היא לא בהכרח רעה, היא נותנת לנו הזדמנות לשחק עם הטכנולוגיה בצורה קלה וללמוד תוך כדי תנועה. ממילא רוב המוצרים לא יעלו לפרודקשן של חנות גדולה ב Black Friday. כן כדאי לזכור:
1. כל עוד כל פעם שאתם נתקעים יש תשובה מהירה ב Stack Overflow - אתם עדיין בירח הדבש
2. כל עוד המערכת שלכם עובדת בלי ששברתם את הראש על שיפורי ביצועים - אתם עדיין בירח הדבש
3. כל עוד אתם יכולים למצוא בקלות מודולים שיפתרו לכם כל בעיה - אתם עדיין בירח הדבש
יש טכנולוגיות שירח הדבש שלהן נגמר אחרי יומיים ועם אחרות אתה מקבל חצי שנה על חוף בקאריביים. אבל שווה לזכור שאורך ירח הדבש הוא לא שיקול טוב בבחירת הטכנולוגיה למוצר הבא שלך.
1 419
# למה split מחזיר אלמנטים ריקים כשיש כפילויות?
הפונקציה split קיימת בהרבה שפות תכנות וברובן היחס לכפילויות זהה. הפונקציה מפצלת מחרוזת לפי תו הפרדה שתעבירו לה, ומחזירה רשימה של כל "החלקים" של המחרוזת. הנה דוגמת פייתון להמחשה:
>>> 'one two three'.split(' ')
['one', 'two', 'three']
מעניין לשים לב מה קורה כשתו ההפרדה מופיע יותר מפעם אחת ברצף:
>>> 'one two three'.split(' ')
['one', '', '', 'two', '', '', 'three']
ברגע ששמתי 3 רווחים בין המילים split זכרה לשקף לי את זה בצורה מעניינת: היא הוסיפה ברשימת התוצאות אלמנטים ריקים במספר של אחד פחות מספר התווים הכפולים. מה קורה פה? למה כשתו ההפרדה מופיע יותר מפעם אחת הוא צריך להוסיף אלמנטים ריקים לרשימה? ולמה כשמעבירים None בתור תו הפרדה כל זה לא קורה?
>>> 'one two three'.split(None)
['one', 'two', 'three']
>>> 'one two three'.split()
['one', 'two', 'three']
ההתנהגות מתבהרת כשאנחנו נזכרים בפונקציה join, שהיא ההיפך של split. פונקציית join של מחרוזת מקבלת רשימה ומדביקה אותה חזרה תוך שימוש במחרוזת המקורית כתו הפרדה:
>>> '-'.join(['yo', 'dog'])
'yo-dog'
בזכות התאים הריקים ברשימה ש split מחזירה, אנחנו יכולים להיות רגועים כשנפעיל join מחדש על אותה רשימה שחזרה מ split ולדעת שנקבל בדיוק את המחרוזת המקורית:
>>> text = 'split and join'
>>> ' '.join(text.split(' ')) == text
True