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

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

אבל תמיד זה עבד

24/10/2024

מיכה שטרית שר-

פעם יכולתי לבלוע כדור ארץ שלם. רכבתי על אופניים כשהידיים באויר. פעם הייתי חיה ואת היית מותק. פעם חייתי באמת.

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

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

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

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

ראספברי פי או VPS או אולי בכלל Serverless - איפה לארח פרויקט צד?

23/10/2024

העלייה של פיתרונות Serverless בשנים האחרונות הביאה לשינוי מעניין בהרגלי פרויקטי הצד שלנו. מצד אחד מאוד קל להעלות פרויקט ל Deno deploy או Next ולקבל איחסון בחינם, מצד שני VPS-ים עולים גרושים ובסכומים של 5-10 דולר לחודש אפשר לקבל שרת שיריץ כמעט כל פרויקט צד שתחשבו עליו, ומצד שלישי מחשבים ביתיים רק הופכים לקטנים וחזקים כשאפילו מכשיר כמו Raspberry Pi מגיע כבר עם 8 ג'יגה זיכרון ומעבד 4 ליבות. הנה כמה שיקולים שאני לוקח בחשבון בפרויקטים קטנים:

המשך קריאה

ניסוי Vue - משיכת מידע מקומפוננטה אסינכרונית

22/10/2024

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

https://asyncvue-nr26xan9r48q.deno.dev/

המשך קריאה

טיפ LLM - איך זה עובד ב X ?

21/10/2024

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

מסתבר שגם כאן Chat GPT וחבריו יודעים לעבוד כמו מנוע חיפוש משודרג ולכוון אותנו לפיתרון. אני ניסיתי את זה עם קלוז'ר ועם ספרייה בשם Reagent. רציתי להבין למה כשאני כותב:

[:p :foo/bar]

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

[:p (str :foo/bar)]

באמת גורם ל Reagent להציג את הטקסט המלא :foo/bar.

וכך הגעתי לקוד של Reagent ולשאלה פשוטה - איפה בתוך ערימת הקוד שם נמצאת הפונקציה שמכינה את האלמנטים לריאקט? ועם זה הלכתי ל Chat GPT ושאלתי את השאלה הבאה:

given reagent code
https://github.com/reagent-project/reagent

what part of the code causes reagent to convert the element's content to HTML, assuming the content is a keyword

that is what part converts the expression [:p :foo/bar] to React's createElement ?

התשובה כללה הפנייה מדויקת לפונקציה שאני צריך שנקראת as-element ולקובץ הרלוונטי שנקרא reagent.impl.template.

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

  1. Check the commit history of the project and list all commit messages related to this line

  2. Check the issues of the project and find me all issues that might have led to this line

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

שלושה צעדים לפיתרון בעיה

20/10/2024

  1. להשתכנע שיש בעיה ולמצוא את הקריטריון לפיתרון - כלומר להבין איך צריך להיראות העולם כשהבעיה פתורה.

  2. להבין מה גורם לבעיה.

  3. להציע שינויים שיביאו אותנו לפיתרון ולבחור את הפיתרון המתאים ביותר.

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

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

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

דברים טובים קורים כשמחכים (נוד 23 ו await)

19/10/2024

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

וכך אני גם קורא את השורה:

await "good things come to those who await"

שעשויה להופיע בתור השורה הראשונה בקובץ JavaScript בתקופה הקרובה. מה שקורה זה ש node 23 יודע לטעון קבצי JavaScript שמשתמשים בכתיב ה import/export בעזרת require, כלומר כשאני כותב בקוד:

const utils = require('./utils.js');

זה הולך לטעון מעכשיו את הקובץ utils.js בין אם הוא משתמש בכתיב import/export או אם הוא משתמש בכתיב CommonJS. נו זה מצוין רק הבעיה שקובץ שכתוב בכתיב ESM עלול לכלול קוד אסינכרוני ברמה העליונה ביותר, ו require לא יכול להריץ קוד אסינכרוני. מה עושים? פה הסיפור קצת מסתבך ובעצם הקוד שכתבתי יטען את utils.js רק אם הוא קובץ CommonJS או אם הוא קובץ ESM שלא מכיל קריאה ל await ברמה העליונה ביותר.

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

לכן כותבי ספריות שלא רוצים שיטענו את הספריות שלהם עם require (כדי שאנשים לא יתעצבנו כשתצא גירסה חדשה שכוללת await ברמה העליונה ביותר), יוכלו להוסיף await כזה מזויף, כלומר:

// prevent people from loading this with "require"
// so their code won't break if I add await in some later time
await "good things come to those who await"

export function twice(x) {
    return x * 2;
}

ההערה מעל אגב היא ניסוח שלי.

כמה מילים על חיבור חברתי ומסך כניסה חדש לאתר

18/10/2024

העליתי אתמול גירסה חדשה של מסך הכניסה לאתר עם אפשרות לכניסה דרך גיטהאב או לינקדאין. מוזמנים לנסות את זה כאן (לא כזה מלהיב, אבל עובד): https://www.tocode.co.il/login

אני משתף כמה הערות טכניות לגבי המימוש:

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

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

def self.create_from_provider_data(auth)
  authorization = Authorization.find_by(provider: auth.provider, uid: auth.uid)
  return authorization.user if authorization

  user = User.find_or_create_by(email: auth.info.email) do |user|
    user.name = auth.info.name || auth.info.full_name
    user.password = Devise.friendly_token[0, 20]
  end

  user.authorizations.create(provider: auth.provider, uid: auth.uid)

  return user
end

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

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

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

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

  4. ובאמת מילה על אתרי הלוגין החברתי - הסיפור זהה בכולם כי כולם משתמשים באותו פרוטוקול: פותחים אפליקציה, מקבלים client_id ו client_secret ומכניסים אותם ליישום שלנו. כן חשוב להגדיר מה כתובת האתר שלנו ביצירת האפליקציה כי הם מוכנים לשים מסך לוגין חברתי רק לאנשים שהגיעו מהאתר שמתאים לאפליקציה שפתחתם. בכל אתר דף ההגדרות בו יוצרים אפליקציה מתחבא במקום אחר, בגיטהאב זה היה ב Developer Settings ובלינקדאין יש פורטל של מפתחים ובו יוצרים אפליקציה. מה שעוד היה מבלבל באתר של לינקדאין זה שצריך לבחור בטאב Products את המוצר Sign In with LinkedIn using OpenID Connect בשביל שהלוגין יעבוד.

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

השתמשו באיזה ספריות שתרצו

17/10/2024

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

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

שתי אפשרויות יותר טובות לדעתי למשימות בית:

  1. אין להשתמש בספריות הרחבה - תכתבו את קוד התקשורת לבד בריאקט ואת העיצוב ב CSS או style. ברור שייקח יותר זמן ולא בטוח שנראה את המוצר הכי מעוצב, אבל אולי נרוויח שיחה מעניינת על הקוד.

  2. השתמשו רק בספריות X, Y ו Z - פה יש סוג של ייתרון לאנשים שכבר מכירים מראש את הספריות שבחרנו למשימה, אבל רוב הזמן בפרונט אנד קל לאנשים להשתמש בספריות רלוונטיות גם אם לא עבדו איתן בעבר. כלומר מי שעבד עם react-query יצליח להסתדר מהר עם swr. מי שעבד עם emotion יצליח להסתדר עם styled components וכו. ברור אל תבחרו פה רידאקס או ספריות שקשה ללמוד, אלא אם כן אתם ספציפית מחפשים לגייס אנשים שמכירים ספריות אלה.

יותר מדי גמישות יכולה לעבוד לרעתכם, גם במשימות בית.

היום למדתי: מערכת הקבצים הסודית של הדפדפנים

16/10/2024

נמאס לכם מהמגבלה של 5 מגה של local storage? רוצים לכתוב ולקרוא מהר לקבצים שיישמרו אבל אתם תקועים בתוך דפדפן? מסתבר שיש פיתרון יחסית חדש ולא מסובך שנקרא Origin private file system או בקיצור OPFS. מנגנון זה מספק לנו משהו שעובד בדיוק כמו מערכת קבצים אבל סגור בתוך הדפדפן. כרום אצלי על המחשב נתן לי Quota של 500 ג'יגה אפילו בלי לבקש רשות.

המשך קריאה

ה vimrc שלי לפיתוח ריילס

15/10/2024

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

call plug#begin()

" List your plugins here
Plug 'tpope/vim-sensible'
Plug 'tpope/vim-rails'
Plug 'ctrlpvim/ctrlp.vim'
Plug 'preservim/nerdtree'
Plug 'nanotech/jellybeans.vim'

call plug#end()

let mapleader = ","

syntax on
filetype on
set number

colo jellybeans

set hidden
set shiftwidth=2
set expandtab
set tabstop=2
set wildmenu

set incsearch
set hlsearch
set ruler
set smartindent

nnoremap <silent> <C-l> :noh<cr>

let g:ctrlp_custom_ignore = '\v[\/]\.(git|hg|svn)$|node_modules\/'

nnoremap <Leader>n :NERDTreeToggle<cr>

הסבר בקצרה:

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

  2. פלאגין ctrl-p מחפש בקבצים מהר, ו nerdtree פותח עץ תיקיות. כן יש יותר חדשים מהם אבל שניהם עובדים לי טוב. שימו לב שלקראת סוף הקובץ אני אומר ל ctrl-p ממה להתעלם וממפה את ההפעלה המהירה של Nerdtree.

  3. ג'ליבינס זו ערכת צבעים מוצלחת.

  4. כפתור הלידר הוא פסיק, כי אני רגיל.

  5. חיפוש אינקרמנטלי עם צבעים מגיע מ incsearch ו hlsearch והמיפוי של Ctrl L עוזר לנקות את ההדגשה של החיפוש.

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

https://github.com/junegunn/vim-plug

יש לכם טיפים והגדרות וים שאתם לא יכולים לחיות בלי? שתפו בתגובות אולי אוכל לאמץ כמה רעיונות.