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

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

זהירות פלסטר - מתוקן

12/11/2024

המתכון הוא פשוט-

  1. כותבים קוד תשתית.

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

  3. מתקנים את התשתית.

  4. בונים את הפיצ'ר עם התשתית החדשה.

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

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

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

לא שוב טייפסקריפט

11/11/2024

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

<script setup lang="ts">
const props = defineProps<
  | {number: number}
  | {text1: string, text2: string}>();

const text1 = "number" in props ? String(props.number) : props.text1;
const text2 = "number" in props ? "Great Number!" : props.text2;
</script>

<template>
  <p>{{ text1 }}</p>
  <p>{{ text2 }}</p>
</template>

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

<Demo :number="5" />
<Demo text1="hello" text2="world" />

זה עבד מבחינת טייפסקריפט אבל הקוד עצמו נכשל. בהגדרת props עם defineProps ב Vue, אוביקט הפרופס תמיד מכיל את כל הפרופס האפשריים, כלומר בשתי ההפעלות הוא יקבל גם את number, גם את text1 וגם את text2, פשוט בהפעלה הראשונה number מקבל את הערך 5 והטקסטים יהיו undefined ובהפעלה השניה זה number שיהיה undefined והטקסטים מקבלים את הערכים הנכונים שלהם.

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

const text1 = props.number ? String(props.number) : props.text1;
const text2 = props.number ? "Great Number!" : props.text2;

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

const props = defineProps<
  | {number: number, text1?: never, text2?: never}
  | {number?: never, text1: string, text2: string}>();

const text1 = props.number ? String(props.number) : props.text1;
const text2 = props.number ? "Great Number!" : props.text2;

הקוד הזה עובד ובנוסף יש לנו בדיקת טייפסקריפט טובה. שתי השורות האלה תקינות:

<Demo :number="5" />
<Demo text1="hello" text2="world" />

ושלושת אלה מראות פס אדום על הקומפוננטה כדי שנזהה הפעלה לא נכונה:

<Demo :number="5" text1="hello" text2="world"  />
<Demo text1="hello"  />
<Demo  />

הבעיה עם עצות גרועות

10/11/2024

הבעיה עם עצות גרועות היא שהן גרועות. טוב תכלס זאת הבעיה היותר קלה איתן.

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

  1. עצה גרועה תשתמש בז'ארגון לא מובן ותנצל פערי ידע בין היועץ למקבל העצה.

  2. עצה גרועה הרבה פעמים כן תפתור בעיה, פשוט לא את הבעיה שלך (זה מה שגורם לה להיות מבלבלת).

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

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

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

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

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

09/11/2024

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

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

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

  1. מה מחובר למה? מאיפה מגיע המידע ולאן הוא הולך? איפה המידע נשמר?

  2. למה אוביקט מסוים נשלח ברשת? למה תוכן מסוים חוזר? איזה חלק בתוכנה אחראי לכל חלק ב HTML?

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

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

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

עכשיו אתם- מתחזקים מערכת Full Stack? על איזה שאלות לא תוותרו לעצמכם ותתאמצו למצוא את התשובות?

עשר שנים של פרונטאנד

08/11/2024

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

  1. פרונטאנד

  2. בקאנד

  3. באנדלרים

  4. מריצי משימות

  5. כלי בדיקות

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

המשך קריאה

דוגמת ויו: משחק איקס עיגול

07/11/2024

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

המשך קריאה

איזה סוג של מכוער

06/11/2024

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

  1. מצאתי באינטרנט דרך שפותרת את הבעיה והעתקתי בלי להבין.

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

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

  4. הפיתרון שלי מטפל רק ב 90% מהמקרים.

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

  6. פתרתי את הבעיה בצורה שתפריע לכל הוספת פיצ'ר חדש למערכת.

  7. פתרתי את הבעיה בצורה שמוסיפה אילוץ חדש למערכת שלא היה שם קודם.

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

לפעמים כפל קוד דווקא יכול להיות יותר קריא

04/11/2024

בואו נראה דוגמה פשוטה מ vue לצמצום כפל קוד ואז ננסה לראות מה דעתנו על השינוי. אני מתחיל עם משתנה בשם seconds ושני משתנים מחושבים בשם minutes ו hours:

const seconds = ref(0);
const minutes = computed(() => {
  get() { return seconds / 60},
  set(minutes) { seconds.value = minutes * 60}
});
const hours = computed(() => {
  get() { return hours / 3600},
  set(hours) {seconds.value = hours * 3600}
})

ואז אני שם לב ש minutes ו hours בעצם חושבו באותה צורה אז אני מארגן מחדש את הקוד:

function deriveTime(start: Ref<number>, factor: number) {
  return computed({
    get() { return Number((start.value / factor).toFixed(2)) },
    set(newValue) { start.value = newValue * factor }
  })
}

const seconds = ref(0);
const minutes = deriveTime(seconds, 60);
const hours = deriveTime(seconds, 3600);

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

const seconds = ref(0);
const minutes = deriveTime(seconds, 60);
const hours = deriveTime(seconds, 3600);

והשאלה - איזה API טוב יותר? כמה מחשבות:

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

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

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

  4. פיתרון טוב יהיה להוסיף תיעוד באתר הקריאה שמסביר מה קורה פה, משהו כזה:

const seconds = ref(0);
// create reactive modifiable computed refs for other time units:
const minutes = deriveTime(seconds, 60);
const hours = deriveTime(seconds, 3600);

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

פיתרון ביניים נוסף שיכול לעזור כאן הוא להעביר רק את יצירת האוביקט לפונקציה אבל להשאיר את הקריאה ל computed באתר הקריאה כלומר:

const seconds = ref(0);
const minutes = computed(deriveTime(seconds, 60));
const hours = computed(deriveTime(seconds, 3600));

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

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

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