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

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

את הטעות הזאת אפילו TypeScript לא הצליח לתפוס

26/01/2023

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

function replaceByDictionary(text: string, replacements: Record<string, string>) {
    let interpolatedText = text;

    for (const [key, value] of Object.entries(replacements)) {
        interpolatedText = interpolatedText.replace(key, value);
    }
}

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

const replacedText = replaceByDictionary("I love TypeScript", { "I": "Everyone" });
await fetch(`https://tocode.requestcatcher.com/test?text=${replacedText}`, { method: 'POST' });

רק כדי להישאר עם לב (וקוד) שבור כשגיליתי שהבקשה שתכל'ס נשלחה לשרת היתה עם ה URL:

POST /test?text=undefined

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

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

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

const replacedText: string = replaceByDictionary("I love TypeScript", { "I": "Everyone" });

ועל זה טייפסקריפט כבר יצעק.

או (ולדעתי עדיף) להחליט להוציא לפונקציה את הקוד ששולח טקסט לשרת, ואז נקבל:

async function postText(text: string) {
    await fetch(`https://tocode.requestcatcher.com/test?text=${replacedText}`, { method: 'POST' });
}

const replacedText = replaceByDictionary("I love TypeScript", { "I": "Everyone" });
await postText(replacedText);

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

שני סוגים של קוד נכון

25/01/2023

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

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

FROM postgres:15.1

במקום:

FROM postgres

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

או ב JavaScript נעדיף לכתוב:

JSON.parse(data);

על פני:

eval('(' + data + ')');

למרות שגם הגירסה השניה עובדת, כי הגירסה הראשונה מהירה יותר ומתמודדת טוב יותר עם מידע שאינו JSON.

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

ורסל מציעה את הדרך הקלה ביותר להעלות יישום Flask לענן

24/01/2023

ורסל (vercel) מפורסמת בזכות הפריימוורק next.js, אותה יצר אותו גיירום ראוך שהקים גם את ורסל. אבל חוץ מפרויקטי ריאקט לורסל יש עוד לא מעט פיצ'רים בשרוול. הם חולמים להיות שכבה מעל AWS שתאפשר הקמה קלה יותר של שירותים מבוססי ענן, ולפחות לפי התקנת יישום flask על התשתיות שלהם - הם מקיימים ובגדול. בואו נראה איך זה עובד.

המשך קריאה

הפיטורים בטק (או: מה עושים עם הזמן הפנוי)

23/01/2023

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

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

המשך קריאה

טיפ לינוקס: כניסה לסרביסים באמצעות SSH Tunnel

22/01/2023

צריכים להתחבר לסרביס שרץ על מכונה אחרת אבל נחסמים בגלל חומת אש? הסרביס המרוחק מקשיב רק לחיבורים מ localhost ואתם מגיעים מבחוץ ובכל זאת רוצים לגשת? אל תמהרו לשנות את הגדרות חומת האש או לשנות את ה listen interface. אם יש לכם גישת ssh למכונה הטריק הפשוט הבא יכול לתת פיתרון הרבה יותר מהיר.

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

בואו נראה דוגמה עם redis - אני מקים שתי מכונות Ubuntu, מתקין redis על שתיהן ומייצר חיבור ssh ביניהן כך שאפשר יהיה ממכונה אחת להתחבר ב ssh למכונה השניה. כתובות ה ip שלי הן 192.168.64.15 ו 192.168.64.13.

קודם כל אני נכנס למכונה 13 ושם מכבה את שרת ה redis כך שחיבור מקומי ייכשל (כדי לא להתבלבל):

$ systemctl stop redis-server
$ redis-cli
Could not connect to Redis at 127.0.0.1:6379: Connection refused

אני גם מריץ את הפקודה הבאה כדי לוודא שיש לי גישת ssh ממכונה 13 למכונה 15:

ssh ubuntu@192.168.64.15 /bin/true

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

$ ssh ubuntu@192.168.64.15
$ redis-cli
redis-cli
127.0.0.1:6379> set x 10
OK

ועכשיו מגיעים לקסם. במכונה 13 אני מפעיל:

$ ssh -L 6379:localhost:6379 ubuntu@192.168.64.15

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

נפתח את מכונה 13 בחלון נוסף ושם אני כותב:

$ redis-cli

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

127.0.0.1:6379> get x
"10"

כלומר הפקודה redis-cli שרצה על מכונה 13 מתנהגת כאילו על המכונה המקומית בפורט 6379 יש שרת redis, ובצד השני החיבור נראה כאילו הוא מגיע מהמכונה המקומית ולכן למרות ש redis מקשיב רק לממשק הרשת המקומי עדיין הצלחנו להתחבר אליו.

שידרוג וובפאק בתיקיית הדוגמאות בקורס ריאקט

21/01/2023

אחד הדברים שאני אוהב בקורס ריאקט פה באתר הוא היצירה של פרויקט מאפס בכל הדוגמאות, כאשר לכל דוגמה יש קובץ webpack.config.js משלה. אני יודע שבעולם האמיתי הרבה פעמים תשתמשו ב vite או create-react-app כדי ליצור את תבנית הפרויקט, ואני אפילו מלמד את הכלים האלה במסגרת הקורס, אבל גם מאמין שכשהדוגמאות פשוטות ואפשר להיכנס לכל קבצי ההגדרות ולהבין מה כל שורה שם זה עוזר להבין איך דברים עובדים.

(ולא, אתם כנראה לא תצליחו להיכנס לכל קבצי ההגדרות של create-react-app כשאתם רק מתחילים את הדרך בריאקט. אבל עם קובץ וובפאק של 20 שורות אני מאמין שתסתדרו).

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

לאחרונה גיליתי בדרך הקשה ש Node הוסיפו משהו שהתנגש עם webpack 4, זה שבר כמה דברים ב create-react-app וכן גם את הגדרות הפרויקט הקטנות שלי. יש פרטים בקישור הזה. בכל מקרה כשדברים נשברים זאת תמיד הזדמנות טובה לשדרג.

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

devServer: {
  overlay: true,
},

ל:

devServer: {
  client: {
    overlay: true,
  }
},

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

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

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

מצאו את המתחזים!

20/01/2023

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

ואני מודה, גם אני פעם נפלתי בבורות האלה.

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

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

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

  1. ספרי על הפרויקט האחרון שבנית.

  2. ספרי על אתגרים מעניינים שנתקלת בהם בפרויקט.

  3. איזה פיצ'ר היה לך קשה לממש ולמה.

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

  5. ספרי על תקלת פרודקשן מעניינת שטיפלת בה.

  6. איך נראה תהליך פיתוח תוכנה אידאלי בינייך?

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

למה כל כך לאט?

19/01/2023

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

  1. לא מכירים מספיק את הטכנולוגיה.

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

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

  4. הפרויקט דורש שינויים בקוד קיים שלא בנוי לשינויים.

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

  1. אחרי שינוי אני לא יודע מה שברתי.

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

  3. הקוד כולל מספר מנגנונים שונים כדי לעשות את אותו דבר (לדוגמה פרויקט ווב שגם כולל Inline Style בקומפוננטות ריאקט וגם CSS).

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

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

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

תשתית בתור קוד והחשיבות של הוראות

18/01/2023

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

מדריך מהיר יגיד לכם ליצור משאבים דרך ה AWS Console, עם משפטים כמו:

In the AWS Console, create a new WebSocket API

מדריך נכון יכלול קוד אוטומטי שיוצר את המשאבים:

policy = iam_resource.create_policy(
    PolicyName=f'{lambda_role_name}-{self.permission_policy_suffix}',
    PolicyDocument=json.dumps({
        'Version': '2012-10-17',
        'Statement': [{
            'Effect': 'Allow',
            'Action': ['execute-api:ManageConnections'],
            'Resource': self.api_arn}]}))
policy.attach_role(RoleName=lambda_role_name)

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