מבט נוסף על Jotai
כתבתי בעבר על jotai ואני מודה שבאותו זמן הוא לא היה ספריית ניהול הסטייט המועדפת עליי. חלפה שנה וחצי והיום אני כבר הרבה יותר אוהב אותו וממליץ עליו לפרויקטים חדשים. בואו נראה למה.
טיפים קצרים וחדשות למתכנתים
כתבתי בעבר על jotai ואני מודה שבאותו זמן הוא לא היה ספריית ניהול הסטייט המועדפת עליי. חלפה שנה וחצי והיום אני כבר הרבה יותר אוהב אותו וממליץ עליו לפרויקטים חדשים. בואו נראה למה.
פליירייט היא ספריית כתיבת בדיקות מקצה לקצה לאפליקציות ווב שמתאימה לפייתון, TypeScript / JavaScript, ג'אווה ודוטנט. בזכות כמה פיתרונות ורעיונות ייחודיים ועוד כמה רעיונות טובים שהם אספו ממקומות אחרים העבודה עם פליירייט מרגישה נכונה מהרגע הראשון. הנה 4 דברים שאני מאוד אהבתי בספריה ואחד שפחות. נתחיל בטובים:
תיעוד - אני יודע זה כאילו לא חלק מהספריה אבל האמת שכשמתחילים לעבוד עם ספריה ומגלים עמודי תיעוד מושקעים שכוללים גם הסבר מפורט, גם וידאו כשצריך וגם מדריכים ו Best Practice אתה כבר מרגיש בידיים טובות.
כתיבת בדיקות באמצעות הקלטה - מתוך שורת הפקודה כתבתי npx playwright test --debug
ונכנסתי למצב הדיבאג של הבדיקות, שם היה לי דפדפן שהראה את העמוד וכפתור "הקלטה" שפשוט מתרגם את הפעולות שלי לקוד בדיקה. נכון הוא לא ידע לכתוב את ה expect כי הוא לא ידע למה לצפות, אבל כל השאר עבד ממש בסדר. זו בדיקה לדוגמה שכתבתי לאתר טוקוד באמצעות המקליט שלהם:
test('sign up to receive daily posts', async ({page}) => {
await page.goto('https://www.tocode.co.il/blog');
await page.getByRole('textbox', { name: 'you@wherever.you.are' }).click();
await page.getByRole('textbox', { name: 'you@wherever.you.are' }).fill('ynon@tocode.co.il');
await page.getByRole('button', { name: 'שלחו לי למייל' }).click();
await expect(page.getByText('נשלח אליך מייל עם קישור לאישור ההרשמה. יש לפתוח את המייל וללחוץ על הכפתור לאישור')).toBeVisible();
})
import { defineConfig, devices } from '@playwright/test'; // import devices
export default defineConfig({
projects: [
{
name: 'chromium',
use: {
...devices['Desktop Chrome'],
},
},
{
name: 'Mobile Safari',
use: {
...devices['iPhone 13'],
},
},
],
});
import { test, expect } from '@playwright/test';
import AxeBuilder from '@axe-core/playwright'; // 1
test.describe('homepage', () => { // 2
test('should not have any automatically detectable accessibility issues', async ({ page }) => {
await page.goto('https://your-site.com/'); // 3
const accessibilityScanResults = await new AxeBuilder({ page }).analyze(); // 4
expect(accessibilityScanResults.violations).toEqual([]); // 5
});
});
await expect(page).toHaveURL('...');
await expect(page.url()).toBe('...');
בראשונה toHaveURL
היא בדיקה אסינכרונית שמחכה עד שה URL ישתנה בעקבות לחיצה או הפניה, אבל הבדיקה השניה היא בדיקה מיידית כי page.url
היא פונקציה סינכרונית ו toBe
היא בדיקה סינכרונית. ה await בשורה השנייה לא עושה כלום כיוון שאין שם שום Promise. בפועל מבנה זה דורש להסתכל על הטיפוסים כל פעם שכותבים בדיקה כדי להבין אם פונקציית הבדיקה היא סינכרונית או אסינכרונית.
סך הכל מאוד אהבתי את פליירייט ואם אתם צריכים ספריית בדיקות End To End לפרויקט אין ספק שמדובר בבחירה טובה ובטוחה.
הנה טריק HTML נחמד שמסתבר שנתמך כבר תקופה ואיכשהו דילגתי עליו - לא צריך יותר JavaScript (או אפילו אם כבר יש לכם JavaScript לא צריך לשמור משתנה סטייט) רק בשביל לנהל אקורדיון עם דברים שנפתחים ונסגרים. כל מה שצריך זה אלמנט details של HTML וזה נראה ככה:
<details >
<summary>Toggle me</summary>
<div >
<p>Look there's no JavaScript involved ... yay</p>
</div>
</details>
והקודפן:
ואם אתם צריכים לעצב אותו אפשר להשתמש ב :open
ב CSS כדי לתפוס את המצב הפתוח (לא עובד בספארי בינתיים) לדוגמה:
details:open > summary {
background-color: pink;
}
:is(select, input):open {
background-color: pink;
}
או ב open בתוך סוגריים מרובעים שכן עובד גם בספארי:
details summary {
background: pink;
}
details {
background: lightgreen;
}
details[open] summary {
background: red;
}
ובנושא אחר - אני פותח היום את הרישום לקבוצת פיתוח הפרויקטים הבאה שתצא לדרך ביוני. זה מסלול מעולה שנותן למשתתפים את המסגרת, הידע והמוטיבציה לבנות פרויקט מקצה לקצה. אם מעניין אתכם שווה להעיף מבט בדף הפרויקט ולקבוע שם פגישת היכרות.
הפיתרון הנכון הוא לא תמיד הקל ביותר. רוב הזמן זה הפוך, הפיתרון הנכון הוא הקשה ביותר ליישום כי הוא דורש ארגון מחדש של הקוד.
הפיתרון הנכון הוא לא תמיד המהיר ביותר לביצוע. רוב הזמן זה הפוך, הפיתרון הנכון לוקח הכי הרבה זמן כי הוא כולל גם זמן לימוד. אם היית יודע איך לעשות את זה נכון היית עושה את זה נכון בפעם הראשונה.
הפיתרון הנכון דורש להודות בטעות, לקחת צעד אחורה ולמצוא דרך מפה לשם. רוב הזמן כשיש לי מערכת שכבר נבנתה בצורה לא נכונה יהיה לי מאוד קשה לעבור לפיתרון הנכון, קשה כמעט כמו לבנות את כל המנגנון מהתחלה.
הפיתרון הנכון הוא הפיתרון הנכון. הוא נכון כי אחרי יישום שלו יהיה יותר קל להתקדם. הפיתרון הנכון לוקח אותך אחורה כדי שתוכל להתקדם מהר יותר. והפיתרון הנכון הוא בלתי נמנע. להמשיך לנסוע עם גלגלי עזר לא באמת יחסוך לך את הצורך ללמוד לרכב על אופניים. ואם ברור מה צריך לעשות אז גם ברור שככל שנחכה זה רק יהיה יותר קשה.
יש רק בעיה אחת.
אתה אף פעם לא יודע שהגעת לפיתרון הנכון, וכנראה שבעולם האמיתי אי אפשר להגיע אליו. פיתרונות לא נכונים? כאלה אני מזהה מקילומטר, זה כח העל שלנו המתכנתים - להיות לא מרוצים ולהגיד כמה קוד מסוים גרוע. הפיתרון הנכון הוא חלום וכנראה לא אחד שאפשר להגשים. בינתיים מה שאפשר לעשות זה לזהות עוד בעיות בקוד ולשפר, באג אחד בכל פעם.
ריאקט תמיד היה מסובך אבל סוג הסיבוך השתנה. פעם היה קשה להבין את הרעיון של ריאקט אבל ה API שלו היה יחסית קטן, בטח בהשוואה לאנגולר. מפתחי ריאקט שהכרתי לפני 8 שנים ידעו בעל פה מה עושה כל פקודה בספריה. היום הסיפור קצת יותר מסובך, ובשביל להוכיח את זה אני רק אזרוק פה רשימה של Hooks מהתיעוד. נסו לספור כמה אתם מכירים או בכמה השתמשתם:
useId
useDeferredValue
useDebugValue
useInsertionEffect
useImperativeHandle
useOptimistic
useSyncExternalStore
useActionState
useFormStatus
הבעיה עם עומס ה Hooks היא שכל Hook נועד לפתור בעיה מאוד ספציפית. חלקם באמת פשוטים כמו useDebugValue
אבל כל אחד מהרשימה הזאת מתאר "מעקף" או בעיה שאי אפשר לפתור עם הכלים הרגילים של ריאקט. בעולם מושלם כל ה-9 האלה וכנראה גם חלק מה Hooks היותר שגרתיים היו בנויים בתור ספריות חיצוניות עבור אותם מקרים שצריכים אותם.
הבאג הזה ב Next עורר דיון סוער:
https://github.com/vercel/next.js/discussions/64660
בקצרה כשאני בונה אפליקציית צד-לקוח בלבד עם output: 'export'
ב next.js אי אפשר להשתמש בנתיבים דינמיים, כלומר נתיב כזה לא יעבוד:
/blog/[slug]
ההסבר הוא שכשנקסט רואה כזה דבר הוא לא יודע איזה קובץ HTML לבנות, כי צריך לזכור שכל מנגנון הניתוב של נקסט באפליקציית צד לקוח בנוי על זה שהוא מריץ את קבצי הקומפוננטות לפי מערכת הקבצים ומייצר מערכת קבצי HTML מקבילה לקבצי ה JavaScript, כלומר מרנדר את כל קבצי ה page.tsx ושומר את התוצאות בתור HTML-ים.
ואם נשים בצד את כל הפיתרונות המתוחכמים ואת כל התקוות שבקרוב החברים ב Vercel יפתרו את זה (הם הבטיחו פיתרון באזור מאי-יוני), אבל כבר בינתיים אפשר לבנות פיתרון מקביל ודי פשוט - במקום להשתמש ב Route Params נשתמש ב Search Params, כלומר הנתיב יהיה:
/blog/post?slug=hello-world
בקוד next קצת נודניק ובשביל לגשת ל Search Params אני צריך לעטוף את הקומפוננטה ב Suspense לכן מבחינת קוד ב page.tsx אני כותב:
'use client'
import ShowPost from './show-post';
import { Suspense } from 'react';
export default function BlogPost() {
return (
<div>
<Suspense fallback={<p>Loading post</p>}>
<ShowPost />
</Suspense>
</div>
)
}
וב show-post.tsx יש לי:
'use client'
import Link from 'next/link';
import { getPost } from '@/lib/blog';
import { useSearchParams, notFound } from 'next/navigation';
export default function BlogPost() {
const slug = useSearchParams().get('slug')!;
const post = getPost(slug);
if (!post) {
return <p>Not flund. slug = {slug}</p>
}
return (
<div>
<h1>{post.title}</h1>
<p><Link href="/blog">Back to blog</Link></p>
<p>{post.fulltext}</p>
</div>
)
}
ובהנחה שאתם יודעים איך לקבל פוסט לפי slug הקוד יעבוד ויש לכם בלוג בתור אפליקציית עמוד יחיד.
אין דבר כזה. מרגע שלקחתי קוד מ AI ושמתי אותו בריפו הוא הופך לקוד שלי. השם שלי כתוב על הודעת הקומיט לא של Gemini. האחריות היא שלי לדאוג שה AI לא עושה שטויות ולקרוא את הדברים שהוא כותב, ואם אני לא מבין משהו האחריות היא שלי ללמוד את זה ולהפעיל חשיבה ביקורתית כלפי ה AI.
מהבחינה הזאת ה AI לא מחדש כלום מעבר ל Stack Overflow או למאגר סקריפטים שמצאתי ברשת. רק בגלל ש AI כתב את זה לא הופך את זה לנכון או רצוי. וכן אני מסכים אתכם שיש פה בעיה כי AI יוצר אינסוף קוד שנראה הגיוני בכלום זמן. בדיוק בשביל זה משלמים לכם יותר ממה שאתם משלמים ל AI.
אם אתם לא מסוגלים להבין את הקוד של ה AI עדיף שתכתבו בעצמכם. ואם אין לכם זמן לקרוא את הקוד של ה AI, איך תמצאו זמן לתקן את הבאגים שלו?
הקוד הזה עובד יפה בפייתון:
import re
if re.search('a', 'hello'):
print("a found in hello")
else:
print("a not found in hello")
אבל זה לא כל כך:
if 'hello'.find('a'):
print("found a")
else:
print("a not found")
למרות שפייתון לא אוהב להמיר טיפוסים באופן אוטומטי, בכל הנוגע לבוליאנים הגישה של פייתון הפוכה, אלה הכללים:
אם יש פונקציית __bool__
לדבר, פייתון יפעיל אותה כדי לגלות מה הערך הבוליאני.
אם אין __bool__
אבל יש __len__
, פייתון יפעיל את __len__
ויבדוק אם התוצאה אינה אפס.
אם אין __bool__
ואין __len__
מחזירים True.
הנה כמה ניסויים:
>>> None.__bool__()
False
>>> (-1).__bool__()
True
מה עם דוגמת הביטוי הרגולארי?
>>> re.search('a', 'hallo')
<re.Match object; span=(1, 2), match='a'>
>>> re.search('a', 'hallo').__bool__()
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
AttributeError: 're.Match' object has no attribute '__bool__'. Did you mean: '__copy__'?
>>> re.search('a', 'hallo').__len__()
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
AttributeError: 're.Match' object has no attribute '__len__'. Did you mean: '__le__'?
לביטוי רגולארי אין __bool__
ואין __len__
לכן הוא מחזיר True. והנה ניסוי עם קלאסים שלי:
class Foo:
def __bool__(self):
return False
class Bar:
pass
f = Foo()
if f:
print("Foo 1")
else:
print("Foo 2")
b = Bar()
if b:
print("Bar 1")
else:
print("Bar 2")
הפלט כצפוי Foo 2
ו Bar 1
.
את חגורות הבטיחות רואים. יש קליק כשהילדים שמים חגורה, וכשמישהו שוכח לשים חגורה האוטו מצפצף. חגורות הבטיחות לא רק מגינות עליך מתאונה הן גם מאוד נוכחות בזמן הנסיעה. אותו דבר המובילאיי שמצפצף כל פעם שאתה קצת יוצא מהנתיב או מתקרב לאוטו שלפניך.
כריות אוויר עובדות אחרת. הן יהיו שם כשנצטרך אבל רוב הזמן אין לנו אינטרקציה איתן.
גם חגורות בטיחות וגם כריות אוויר דורשות מידה לא מבוטלת של אמון. אנחנו מקווים שכשתהיה תאונה הן יעבדו, אבל אין דרך להיות בטוחים מראש. אף אחד לא יעשה תאונה רק בשביל לראות שחגורת הבטיחות באמת מחוברת כמו שצריך.
בקוד המצב שונה ויש לנו את הפריבילגיה לבדוק את מנגנוני ההגנה שלנו לפני רגע האמת. כשגיטהאב מציעים Push Protection שיזהה אוטומטית ויחסום כשאנחנו מנסים לדחוף סיסמאות או סודות לריפו, אנחנו יכולים לבדוק את זה עם סוד מזויף רק בשביל לראות שהוא חוסם אותנו. כשאנחנו מקימים מערכת גיבוי לבסיס הנתונים, אפשר ורצוי לקחת יום ולנסות לשחזר כדי לראות שהגיבוי באמת יהיה שם כשנצטרך. אם יש לי כמה שרתים ו Load Balancer, אני יכול (ואפילו כדאי) פעם בכמה זמן להריץ עומס יזום על אחד השרתים כדי לראות שה Load Balancer מתפקד ומפסיק להכניס תנועה לשרת העסוק.
רק בגלל שאנחנו רואים משהו כל הזמן לא אומר שהוא עובד (ורק בגלל שאנחנו לא רואים משהו לא אומר שהוא מבוטל). כל מנגנון הגנה דורש תחזוקה ובדיקה שוטפת, ועדיף כשהעניינים רגועים ולא במצב חירום.
תסתכלו על עצמכם היום ועל עצמכם של לפני חמש שנים ותענו בכנות, האם החמש שנים האחרונות היו באמת חמש שנים ניסיון, או שנה אחת שחזרה על עצמה חמש פעמים? הנה כמה סיבות בגללן התשובה השניה עשויה להיות נכונה:
הסטאק הטכנולוגי במקום עבודה צריך להתאים לפרויקטים שכבר יש שם. נכון מדי פעם אפשר להכניס כלי חדש, אבל ארכיטקטורה חדשה לגמרי מאפס שלא בטוח תעבוד? פחות קורה (מיקרו סרביסס קצת איפשרו לנו לרוץ קדימה עם טכנולוגיה גם בחברות וותיקות, אבל גם שם הדברים מתכנסים. אף אחד לא רוצה שכל מיקרו סרביס יהיה כתוב בשפה אחרת).
מי שעושה לך Code Review בעבודה לא ממש כותב קוד - מנהלים החל מדרגה מסוימת מפסיקים לכתוב קוד, ולכן קשה להם להביא Insights לגבי שיטות עבודה חדשות שאולי יכולות לעזור.
חלוקה לצוותים ולמשימות - מקום עבודה צריך לייצר קוד ולכן טבעי שאנשים שטובים במשהו ימצאו את עצמם יותר במשבצת בה הם טובים. יודעת לכתוב שאילתות? בואי תבני קוד צד שרת; יודעת לבנות ממשקים? יש מקום בצוות הפרונט. אבל בפרויקט תוכנה כל ההיבטים של הפיתוח קשורים אחד לשני ולפעמים ההתקדמות הנחוצה בניסיון תבוא דווקא מעבודה על הדבר שאת עדיין לא מספיק טובה בו.
חלוקה לצוותים ולמשימות 2 - ניסיון עשיר אומר שראית הרבה מקרים ואתה מצליח לזהות תבניות ולהסיק מסקנות מכל המגוון, מהדומה ומהשונה. במקום עבודה יש לנו פרויקט או כמה פרויקטים מרכזיים שנכתבו במתודולוגיה דומה. אפילו AI לא מצליח ללמוד כשאין מספיק קלטים.
כן מתכנתים לומדים דרך פיתוח פרויקטים, כן המדד של שנות ניסיון הוא חשוב אבל הרבה פעמים לא מספיק. בשביל ללמוד אנחנו רוצים לגלות דברים חדשים, להתמודד עם אתגרים וכן לטלטל את הסירה כדי להבין איזה חלקים יציבים ואיזה חלקים דורשים חיזוק.
היום סיימנו את השבוע הראשון בקורס ליווי פרויקטים. העבודה על הקורס ועם המפתחים המוכשרים שנכנסו למחזור הראשון מלמדת אותי המון על פרויקטים, פיתוח ואיך באמת לגדול כמפתחים. המחזור הבא ייפתח ביוני והרישום אליו ייפתח באפריל. שווה לשמור את התאריך.