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

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

הפחד מעבודה לא מושלמת

24/01/2025

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

המחשבות שגורמות לפחד נעות סביב:

  1. יש רק הזדמנות אחת וחבל לי לפספס אותה.

  2. אם אזרוק את מה שבניתי תהיה לי יותר מוטיבציה לנסות שוב.

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

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

אף פעם לא קל לשנות סיפורים בראש אבל הנה כמה רעיונות ששווה לנסות לאמץ במקום:

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

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

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

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

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

היום למדתי: v-bind ו style scoped

23/01/2025

ויו מאפשר להשתמש ב v-bind בתוך בלוק CSS כדי לחבר קוד עיצוב למידע ריאקטיבי. זה עובד ממש נחמד וזאת הדוגמה מהאתר שלהם:

<script setup>
import { ref } from 'vue'
const theme = ref({
    color: 'red',
})
</script>

<template>
  <p>hello</p>
</template>

<style scoped>
p {
  color: v-bind('theme.color');
}
</style>

אבל מה קורה כשמנסים את זה בלי scoped? כלומר מה אם ננסה:

<script setup>
import { ref } from 'vue'
const theme = ref({
    color: 'red',
})
</script>

<template>
  <p>hello</p>
</template>

<style>
p {
  color: v-bind('theme.color');
}
</style>

בשביל להבין את התשובה צריך להבין איך v-bind בתוך CSS עובד - כש vue רואה v-bind בתוך CSS הוא מגדיר משתנה CSS על האלמנט הראשי של הקומפוננטה עם הערך שכתוב בתוך ה v-bind והביטוי המלא v-bind('theme.color) מוחלף בשיערוך המשתנה.

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

כשהמחשב שוכח להגיד שיש בעיה

22/01/2025

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

הסיפור שלי היום הוא על התרגיל הכי קל בינתיים ב Advent Of Code 2024 הוא יום מספר 3. בתרגיל הזה הקלט מורכב מרצפים כאלה:

xmul(2,4)&mul[3,7]!^don't()_mul(5,5)+mul(32,64](mul(11,8)undo()?mul(8,5))

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

mul(2,4), mul(8,5)

והסכום הוא 48. קל? בטח. אפשר למחוק את כל מה שבין don't ל do ולסכום את מה שנשאר. הנה ה Ruby הראשון שניסיתי לכתוב:

str = File.read('input.txt').gsub(/don't\(\).*?(?:do\(\)|$)/, '')
pp str.scan(/mul\((\d{1,3}),(\d{1,3})\)/).map { _1.to_i * _2.to_i }.sum

רואים את הבעיה? קחו רגע לחשוב על זה. לי זה לקח אפילו יותר מרגע.

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

3.3.5 :010 > str = "one\ntwo\nthree\n"
 => "one\ntwo\nthree\n"
3.3.5 :011 > puts str.gsub(/t/, 'T')
one
Two
Three
 => nil

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

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

str = File.read('input.txt').gsub(/don't\(\).*?(?:do\(\)|\z)/m, '')
pp str.scan(/mul\((\d{1,3}),(\d{1,3})\)/).map { _1.to_i * _2.to_i }.sum

או בגירסת שורה-אחת כמו שאוהבים ברובי:

pp File.read('input.txt')
  .gsub(/don't\(\).*?(?:do\(\)|\z)/m, '')
  .scan(/mul\((\d{1,3}),(\d{1,3})\)/)
  .map { _1.to_i * _2.to_i }
  .sum

תוכנית המנטורינג לבניית פרויקט - פרטי המסלול

21/01/2025

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

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

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

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

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

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

מבנה הפרויקט כל משתתף ומשתתפת יעבדו על פרויקט משלהם בטכנולוגיה לבחירתם, וכן אני מצפה שיהיה סלט טכנולוגי מעניין. יחד עם זאת מבנה העבודה על הפרויקט יהיה דומה אצל כולם: בשבוע הראשון נבנה את הגירסה הראשונה על מחשב הפיתוח, בשבוע השני נעבוד על Deployment ו CI/CD ונקים Github Actions כדי לוודא שיש לנו דרך מהירה מפיתוח לפרודקשן והחל מהשבוע השלישי נתחיל לשלב פיצ'רים מתקדמים יותר של סביבת הפיתוח שבחרנו כמו Queues, Caches, Load Time Optimisations, ושאר רעיונות שיהפכו את הפרויקט שלנו למעניין. השבוע הרביעי יוקדש לבניית דף נחיתה ובדיקות אוטומטיות לפרויקט כדי שיהיה קל יותר למפתחים אחרים להצטרף ולתרום.

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

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

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

הרעיון הגדול הבא

20/01/2025

ברור שיהיה טוב אם הרעיון הבא שלך יהיה להיט ויראלי. ברור שהיית רוצה לכתוב את הוורדל הבא, את הטוויטר הבא, את הפלאפי בירד הבא או אפילו את ה tailwind הבא. ברור.

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

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

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

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

מה ההבדל בין npm create ל npx?

19/01/2025

מה ההבדל בין npm create ל npx ? ולמה בשביל ליצור פרויקט חדש אני מפעילה npm create אבל בשביל להריץ שרת מקומי זה npx static-server? אפשר לוותר על אחד מהם?

אם יש משהו ש npm יודעים לעשות זה להוסיף עוד ועוד פקודות וקיצורים לפקודות שעושים דברים מאוד דומים. בואו נראה מה הסיפור של npx ו npm create.

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

npx cowsay hello

הפקודה השניה, npm create היא למעשה קיצור דרך לפקודת npm init הישנה והטובה. פקודת init נוצרה כדי ליצור מהר קבצי package.json, דרך מענה על מספר שאלות מובנות. היא איפשרה יצירה מהירה של פרויקטים חדשים ב node במקום שנעתיק קובץ package.json כל פעם מפרויקט אחר. החל מגירסה 6.1 פקודת init הסכימה לקבל שם של חבילה שתהיה אחראית על יצירת הפרויקט. באופן אוטומטי init מוסיפה את התחילית create לשם שנתנו לה, מחפשת חבילה ב npm בשם הזה ומפעילה אותה עם npx, כלומר כשאני כותב:

npm create vite hello-world

אני בעצם מריץ את הפקודה:

npx create-vite hello-world

תודה שאמרת לי

18/01/2025

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

<a href="{{ url_for('tasks') }}">All Tasks</a>

אבל AI שכותב קוד, למרות שהוא מכיר את url_for יותר טוב ממך, עדיין יכול לכתוב:

<a href="/tasks">All Tasks</a>

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

כשזה קורה התגובה הראשונה שלי היא להתעצבן על המחשב - מה זה אומרת תודה שאמרת לי!? אם ידעת ש url_for עדיף למה לא כתבת את זה מההתחלה???

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

Please perform a full code review

רוב הזמן באיטרציה השניה נקבל קוד ברמה יותר גבוהה.

ההערה הזאת היא בכלל פיצ'ר

17/01/2025

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

Consider adding rate limiting to the search endpoint

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

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

אני חושב שיותר נכון להסתכל על הערה מהסוג הזה בתור Feature Request יותר מאשר הערת מימוש. במקרה של Rate Limit זה משהו שצריך לעלות בעקבות Security Review על גירסה. לפעמים ה Security Review קורה על כל גירסה לפני העלאה ואז נקבל דוח ונתרגם אותו למשימות שיהפכו לקומיטים, לפעמים ה Security Review יבוא אחרי מתקפת ה DoS הראשונה.

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

חידת Vue: ריאקטיביות

16/01/2025

נתון קוד Vue הבא:

<script setup lang="ts">
import {ref, computed} from 'vue';

const data = { count: 0 };
const value = ref(data);
function btn1() {
  data.count = 5;
}

function btn2() {
  value.value.count++;
}
</script>

<template>
  <div>
  <p>
    Value is: <span>{{ value.count }}</span></p>
  <button @click="btn1">Button 1</button>
  <button @click="btn2">Button 2</button>
  <hr />
</div>
</template>

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

מה יקרה אם נשנה את התבנית ל:

<template>
  <div>
  <p>
    Value is: <span>{{ data.count }}</span></p>
  <button @click="btn1">Button 1</button>
  <button @click="btn2">Button 2</button>
  <hr />
</div>
</template>

מה יהיו ערכי המשתנים אחרי לחיצה על הכפתורים? מה יופיע על המסך?

טיפ טייפסקריפט: יבוא קבצי JSON גדולים

15/01/2025

לטייפסקריפט יש פיצ'ר ממש חמוד לעבודה עם קבצי JSON שנקרא resolveJsonModule. אנחנו מגדירים ב tsconfig.json את האופציה באופן הבא:

{
  "compilerOptions": {
    "resolveJsonModule": true,
    "esModuleInterop": true
  },
  "include": ["src"]
}

ואז כותבים:

import settings from './settings.json';

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

[{
  "repo": "TypeScript",
  "dry": false,
  "debug": false
}]

ואני אנסה לכתוב בקוד:

import settings from './settings.json';
console.log(settings[0].foo);

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

בעבודה עם קבצי JSON גדולים שווה להכיר את האופציה allowArbitraryExtensions של טייפסקריפט, אופציה שבעזרתה נוכל להגדיר את הטיפוס של ה JSON ידנית, נחסוך לטייפסקריפט עבודה וכך נקבל VS Code מהיר יותר והגדרות טיפוסים אמינות. ה tsconfig.json נראה ככה:

{
  "compilerOptions": {
    "allowArbitraryExtensions": true,
    "resolveJsonModule": true,
    "esModuleInterop": true
  },
  "include": ["src"]
}

ובתיקיית src אנחנו יוצרים קובץ בשם של ה json אבל עם d באמצע וסיומת ts, לדוגמה אם ל json שלי קוראים deposits.json אז אני יוצר קובץ בשם deposits.d.json.ts. בקובץ הגדרת הטיפוס אני כותב:

export interface SavingsData {
  INTERESTSDATE: string;
  SAVINGSPROGRAMBYAGE: string;
  AGE: string;
  BANK: string;
  SAVINGSPLAN: string;
  SAVINGSPERIOD: string;
  FIXEDWITHOUTLINKAGEVAL: number;
  FIXEDLINKEDCONSUMERPRICEINDVAL: number;
  VARIABLESPREAD: number;
}

declare const array: Array<SavingsData>;
export default array;

ומפה אנחנו מסודרים. אפשר לייבא את הקובץ deposits.json אפילו שהוא שוקל 10 מגה וטייפסקריפט ישתמש בהגדרת הטיפוס שבנינו במקום לקרוא את הקובץ ולנסות להבין את הטיפוס לבד.