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

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

עוד 3 פיצ'רים של ES13 בהם אתם אשכרה הולכים להשתמש

31/01/2023

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

המשך קריאה

ואז הם הציעו "בואו נחליף את ה Backend"

30/01/2023

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

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

בשביל הדוגמה נדמיין Service שכתוב בריאקט ומתחבר לשרת Backend, אבל גם מתחבר לכל מיני APIs ברשת. בקוד יש כמה מאות קומפוננטות, חלקן לוקחות מידע מה Backend דרך SWR, חלקן מעדכנות מידע באמצעות fetch וקומפוננטות אחרות משתמשות בממשקים ייעודיים של APIs חיצוניים כדי לתקשר עם אותם APIs. ועכשיו נשאל "איפה אני מוצא רשימה של כל בקשות הרשת שה Service שלי מסוגל להוציא?"

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

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

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

בואו נבנה לוח מודעות ב Rails בלי JavaScript

29/01/2023

אחת הדוגמאות הראשונות שאני מראה ל Web Sockets היא בניה של לוח מודעות משותף לכמה גולשים - כל פעם שגולש אחד שולח הודעה, ההודעה מיד מופיעה על המסך של כל האחרים. והיום ננסה לבנות את אותו לוח מודעות בלי לכתוב שורה של JavaScript, רק באמצעות המנגנונים המובנים ב Rails.

המשך קריאה

מעקפים

28/01/2023

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

function isInCanvas(x, y) {
    if (typeof x === 'string') {
        x = Number(x);
    }
    if (typeof y === 'string') {
        y = Number(y);
    }

    // check if (x,y) is in the canvas
}

וכן יש שתי בעיות עם מעקפים:

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

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

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

פיצ'ר חשוב אבל לא בשבילי

27/01/2023

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

אבל אם באותו React את לא יודעת מה זה Server Side Rendering או למה להשתמש ב useDeferredValue, לא קרה כלום. זה אולי יהיה מכשול בראיונות עבודה אבל בחיים האמיתיים יש הרבה פרויקטים שלא צריכים את הפיצ'רים האלה, ויותר מזה - יש הרבה פרויקטים בהם בגלל שינסו להטמיע את הפיצ'רים האלה הפרויקט רק יהפוך ליותר קשה לתחזוקה.

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

את הטעות הזאת אפילו 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 מקשיב רק לממשק הרשת המקומי עדיין הצלחנו להתחבר אליו.