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

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

ואל טאון הוא הפרויקט הכי מדליק שאתם לא מכירים

31/01/2025

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

המשך קריאה

היום למדתי: חיפוש פודקסטים ב podcast index

30/01/2025

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

בחזרה ל val town בשביל לשחק עם הבוט ביקשתי שיכתוב פונקציה לחיפוש פודקסטים ב podcast index. למרבה ההפתעה התוצאה עבדה בניסיון הראשון, כולל הטריק מה API שלהם עם ה sha1 למפתח. זה הקוד:

import crypto from "https://esm.sh/crypto-js";

export default async function searchPodcasts(query: string) {
  const apiKey = Deno.env.get("PODCAST_INDEX_API_KEY");
  const apiSecret = Deno.env.get("PODCAST_INDEX_API_SECRET");

  // Generate authentication headers as per Podcast Index API requirements
  const currentTime = Math.floor(Date.now() / 1000);
  const hashString = apiKey + apiSecret + currentTime;
  const hash = crypto.SHA1(hashString).toString();

  const headers = {
    "X-Auth-Key": apiKey,
    "X-Auth-Date": currentTime.toString(),
    "Authorization": hash,
    "User-Agent": "ValTownPodcastSearch/1.0",
  };

  try {
    const response = await fetch(`https://api.podcastindex.org/api/1.0/search/byterm?q=${encodeURIComponent(query)}`, {
      method: "GET",
      headers: headers,
    });

    if (!response.ok) {
      throw new Error(`HTTP error! status: ${response.status}`);
    }

    const data = await response.json();

    return data.feeds.map(podcast => ({
      title: podcast.title,
      description: podcast.description,
      url: podcast.url,
      artwork: podcast.artwork,
    }));
  } catch (error) {
    console.error("Podcast search error:", error);
    return [];
  }
}

console.log(await searchPodcasts("syntax"));

ואם יש לכם 5 דקות תוכלו גם ליצור חשבון ב val town ולהריץ אותו ישר מהממשק שלהם בקישור:

https://www.val.town/v/ynonp/righteousLimeGalliform

רק שימו לב שצריך קודם ליצור את שני משתני הסביבה PODCAST_INDEX_API_KEY ו PODCAST_INDEX_API_SECRET דרך הממשק של val town לחשבון שלכם בקישור:

https://www.val.town/settings/environment-variables

ובשביל לקבל את המפתחות יש ליצור חשבון ב podcast index api:

https://api.podcastindex.org/

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

חידת Vue - איך עובד Scoped Style

29/01/2025

נכתוב את הקוד הבא בקומפוננטת App.vue:

<script setup>
import Child from './Child.vue';
</script>

<template>
  <p class="red">Hello World from App</p>
  <Child />
</template>

<style scoped>
.red {
  color: red;
}
</style>

וזה Child.vue:

<script setup></script>

<template>
  <button class="red">
    Hello World from Child
  </button>
</template>

אפשר לראות את הדוגמה לייב בקישור הזה: https://tinyurl.com/4nff62kp

עכשיו לשאלות - למה הטקסט בכפתור מופיע באדום? ואיך אפשר "לנתק" אותו מהעיצוב של App כדי שלא יקבל את הגדרת האדום משם?

שימו לב שהגדרות העיצוב הן בבלוק styled scoped ושזו לא הבעיה שמתוארת כאן: https://www.tocode.co.il/blog/2021-10-watch-out-inherited-css, למרות שגם הבעיה בפוסט ההוא מעניינת.

ואם בכל זאת אני רוצה להעביר משתנה ריאקטיבי ב Vue לילדים?

28/01/2025

הקוד הבא ב Vue לא עובד, או לפחות לא עושה את מה שהתכוונתי. קומפוננטה עליונה:

<script setup>
import { ref } from 'vue'
import Child from './Child.vue';
const x = ref(10)
</script>

<template>
  <Child :x="x" />
</template>

וקומפוננטת Child:

<script setup lang="ts">
const {x} = defineProps<{x: any}>();

function inc() {
  x.value++;
}
</script>

<template>
  <p>x = {{x}}</p>
  <button @click="inc">+1</button>
</template>

נראה כאילו Child מקבל אוביקט ריאקטיבי x ומעלה את ערכו ב-1. בפועל העברת הפרמטר דרך props מעבירה את ה value של האוביקט הריאקטיבי, כלומר בתוך Child המשתנה x הוא מספר. למספר אין שדה .value ולכן הקוד נכשל.

המשך קריאה

תבנית פרויקט: nuxt, drizzle, auth0

27/01/2025

אתמול פירסמתי פה תבנית לפרויקט React שמשתמש ב Next.js ומכיל קומפוננטות צד שרת, משיכת מידע מבסיס נתונים וניהול משתמשים עם auth0. היום נראה את החלק השני של הפוסט ונבנה את אותה תבנית עבור nuxt ליישומי vue. הקוד כאן:

https://github.com/ynonp/nuxt-drizzle-auth0-demo

בואו נראה מה יש בריפו.

המשך קריאה

תבנית פרויקט: next, drizzle, auth0

26/01/2025

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

https://github.com/ynonp/next-drizzle-demo

בואו נראה מה יש בפנים.

המשך קריאה

מה עושים עם הקוד שלא מבינים?

25/01/2025

דילמה אמיתית. והיא תמיד היתה.

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

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

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

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

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

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

לכן גם בפרויקט פרודקשן העתקת קוד בלי להבין מ Tutorial או מ Chat GPT פוגעת בפרודוקטיביות לטווח ארוך.

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

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

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

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