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

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

למה קשה לחשב שברים

09/01/2025

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

המשך קריאה

ניסוי ריאקט: משתנה סטייט לקריאה וכתיבה

08/01/2025

ב Vue אנחנו יוצרים משתנה סטייט עם הפקודה ref ומקבלים משהו שאפשר לכתוב אליו וגם לקרוא ממנו לדוגמה הקוד הבא מציג מונה לחיצות עם משתנה סטייט ששומר את מספר הלחיצות:

<script setup>
import { ref } from 'vue'

const counter = ref(0)
function inc() { counter.value++ }
</script>

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

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

המשך קריאה

תרגיל טייפסקריפט: פעולת עדכון גנרית ב Redux

07/01/2025

בדוגמת ההתחלה המהירה של Redux Toolkit הם מציעים את הקוד הבא עבור סלייס של מונה:

import { createSlice } from '@reduxjs/toolkit'

const initialState = {
  value: 0,
}

export const counterSlice = createSlice({
  name: 'counter',
  initialState,
  reducers: {
    increment: (state) => {
      state.value += 1
    },
    decrement: (state) => {
      state.value -= 1
    },
    incrementByAmount: (state, action) => {
      state.value += action.payload
    },
  },
})

// Action creators are generated for each case reducer function
export const { increment, decrement, incrementByAmount } = counterSlice.actions

export default counterSlice.reducer

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

export interface CounterSliceState {
  value: number
  status: "idle" | "loading" | "failed"
  foo: string
  bar: number
  buz: Array<string>
}

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

המשך קריאה

מה מודדים

06/01/2025

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

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

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

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

כשהשפה עובדת אתך - פיתרון Advent Of Code 2024 יום ראשון ב Ruby

04/01/2025

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

המשך קריאה

חידת Vue משתנים ריאקטיביים

03/01/2025

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

<script setup>
import { ref } from 'vue'
const init = {count: 0}
const item = ref(init)

function inc() { item.value.count++ }
function reset() { item.value = init }
</script>

<template>
  <p>{{ item.count }}</p>
  <button @click="inc">+1</button>
  <button @click="reset">Reset</button>
</template>

משתמש לוחץ כמה פעמים על כפתור הפלוס ואז על כפתור ה Reset. האם הערך בתיבה מתאפס? למה?

וכן הכנתי לכם את זה כבר בלייב שתוכלו לבדוק כאן: דוגמת לייב

המשך קריאה

מבחן ה ack

02/01/2025

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

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

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

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

  1. CertificatesController

  2. CertificatesListView

  3. isCertificateReady

  4. certificateStatus

  5. UserInfoForCertificate

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

אני עוד אתגעגע ל Facade

01/01/2025

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

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

import urllib.request
import json

def get_data_using_urllib():
    url = "https://api.example.com/data"

    # 1. Create a request object (so you can set headers if you need)
    req = urllib.request.Request(url, headers={"Accept": "application/json"})

    try:
        # 2. Open the URL
        with urllib.request.urlopen(req) as response:
            # 3. Check the status code
            if response.status == 200:
                # 4. Read and parse JSON data
                raw_data = response.read()
                data = json.loads(raw_data)
                print("Data fetched using urllib:", data)
            else:
                print(f"Error: status code = {response.status}")
    except Exception as e:
        print("An error occurred:", e)

if __name__ == "__main__":
    get_data_using_urllib()

תוכנית שניה עם requests:

import requests

def get_data_using_requests():
    url = "https://api.example.com/data"

    try:
        # 1. Make a GET request
        response = requests.get(url)

        # 2. Raise an exception if the status is not 2xx
        response.raise_for_status()

        # 3. Parse JSON in one step
        data = response.json()
        print("Data fetched using Requests:", data)

    except requests.exceptions.RequestException as e:
        print("An error occurred:", e)

if __name__ == "__main__":
    get_data_using_requests()

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

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

ריאקט מול ויו - שימו לב לשינויי סטייט

31/12/2024

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

בריאקט הקומפוננטה הבאה מגדירה שני ערכים ושני ערכים מחושבים:

import "./styles.css";
import { useState } from "react";

export default function App() {
  const [x, setX] = useState(0);
  const [y, setY] = useState(0);
  const twoX = x * 2;
  const twoY = y * 2;

  return (
    <div className="App">
      <label>
        X:
        <input
          type="number"
          value={x}
          onChange={(e) => setX(Number(e.target.value))}
        />
        <span>x = {x}; </span>
        <span>2x = {twoX}</span>
      </label>
      <label>
        Y:
        <input
          type="number"
          value={y}
          onChange={(e) => setY(Number(e.target.value))}
        />
        <span>y = {y}; </span>
        <span>2y = {twoY}</span>
      </label>
    </div>
  );
}

אבל לא היינו כותבים קוד כזה בעולם האמיתי. הרזולוציה לא נכונה. בדוגמת הקוד גם כש x משתנה וגם כש y משתנה יש לחשב מחדש את שני הערכים המחושבים twoX ו twoY, בגלל שרזולוציית שינויים בריאקט היא קומפוננטה. בעולם האמיתי הקומפוננטה הזאת היתה מחולקת לשתי קומפוננטות, גם בגלל כפל הקוד וגם ובעיקר בגלל ה State - לקומפוננטה יש מספר אזורים שכל אחד מהם מושפע באופן עצמאי מסטייט אחר, וזה סימן ברור שמשהו בבחירת הקומפוננטה שלנו לא נכון.

ב Vue הסיפור קצת יותר מורכב אבל גם יותר קל למשתמשים. זאת הקומפוננטה:

<script setup>
import {ref, computed} from 'vue';
const x = ref(0);
const y = ref(0);
const twoX = computed(() => x.value * 2);
const twoY = computed(() => y.value * 2);
</script>

<template>
  <div class="App">
      <label>
        X:
        <input
          type="number"
          v-model="x"
        />
        <span>x = {{x}}; </span>
        <span>2x = {{twoX}}</span>
      </label>
      <label>
        Y:
        <input
          type="number"
          v-model="y"

        />
        <span>y = {{y}}; </span>
        <span>2y = {{twoY}}</span>
      </label>
    </div>
</template>

הקוד זהה אבל ההתנהגות שונה - שינוי ב x גורם לחישוב מחדש רק של twoX ושינוי ב y גורם לחישוב מחדש רק של twoY, כלומר רזולוציית השינוי היא לפי המשתנה והערכים שמחושבים ממנו. זה אומר שב vue יהיו לנו פחות בעיות ביצועים כתוצאה משינוי שיצא משליטה, אבל מצד שני יותר קשה לנו להבין את ההשפעה האמיתית של כל שינוי כי בשביל לראות מה מושפע מ x צריך לעקוב אחרי ה computed ולהסתכל על ה render function שנוצרה ואיזה חלקים ממנה מושפעים מכל ref.

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