• בלוג
  • עמוד 5
  • לפעמים כפל קוד דווקא יכול להיות יותר קריא

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

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 ולכן אפשר לשנות את הדקות והשעות.

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

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