es
Feedback
ToCode

ToCode

Ir al canal en Telegram

טיפים קצרים למתכנתים מאת ינון פרק

Mostrar más
1 423
Suscriptores
+124 horas
+27 días
+630 días
Archivo de publicaciones
ToCode
1 423
📌 צריכים מפה האתגר המרכזי במבוך הוא כשאתה לא מזהה שהיית במקום מסוים או איך הגעת לשם. משהו נראה מוכר והמחשבה מנקרת, הייתי פה כבר? זה אותו חדר או רק דומה לו? בעבודה עם AI היום יש לי לפעמים תחושה דומה. אתה מדבר עם המכונה ולא בטוח מה היא תעשה. אתה מתקן דבר אחד ולא בטוח מה שברת. אתה מחבר שני חוטים רק בשביל לנתק 5 אחרים. מה שהופך את המבוך למבלבל זה שאפשר להסתובב זמן ארוך בשטח קטן. המבנה המטעה בכוונה של המבוך מעודד הליכה בסיבובים. הרי אם הקירות היו ממוספרים אפשר היה לעקוב אחרי הסימונים ולבנות מפה, לדעת בכל רגע איפה אני ואיפה אני רוצה להיות. מה שהופך את הקידוד עם AI למבלבל זה בדיוק אותה מפה חסרה. התקרבתי או התרחקתי מהיעד? איפה אני בעצם? מצפן לא עוזר להסתדר במבוך. המבוך מבלבל בכוונה, מלא קירות ומעברים עקומים. מצפן אולי נותן תחושת ביטחון אבל בלי המפה, בלי ההבנה איפה אני נמצא ומה המגבלות והקירות שסביבי יהיה קשה למצוא את היציאה. בעולם של תכנות עם AI הפידבק של מודלים אחרים נשמע לי לפעמים כמו מצפן, "כן אתה בכיוון זאת ארכיטקטורה נהדרת" אומר לי קודקס על הקוד שקלוד כתב, ומשאיר אותי עם הבאגים ותחושת ביטחון זמנית. הכח של המפה הוא היכולת להגיד: "אני פה, אני רוצה להיות שם, וזאת הדרך שאני צריך לקחת". סוכני קידוד אלופים בלהביא אותי מהר לנקודה שאני מתאר, אבל בלי לתאר במדויק את נקודת היציאה הם לא יצליחו להביא אותי לשם. היום דיבגתי בעיית ארכיטקטורה שיצר סוכן קידוד. זאת ארכיטקטורה שאין מפתח בעולם שהיה בוחר, ובכל זאת היא נכתבה בקוד כי תיאור המטרה לא היה מספיק ברור ואף אחד לא בדק שהיעד באמת היה היעד שתכננו. יום אחד, אחרי שההייפ יעבור, אנחנו נתעורר ונשאל "מה המפה שלנו?". איך אנחנו יכולים להסתכל על מערכת, להסתכל על פיצ'ר ולתאר במדויק את הקוד שמממש את הפיצ'ר הזה. עם סוכני קידוד אנחנו כבר לא צריכים להקליד את התווים של הקוד. אבל כן חייבים לדעת אם הקוד שקיבלנו הוא הקוד שרצינו.

ToCode
1 423
📌 איך ללמוד אבטחת מידע ולמה זה מסובך חבר שלח מצגת אבטחת מידע שהוא קיבל. המצגת מקיפה, מעניינת, וחסרת ערך. היא חסרת ערך כיוון שאבטחת מידע היא קודם כל שינוי גישה, היא ההבנה האינטואיטיבית שכשלים מסוימים במערכת יכולים לשבור את אותה מערכת בדרכים יצירתיות. אתה יכול להקשיב לעשרות הרצאות שיראו לך מה זה SQL Injection ולמה אסור לחבר מחרוזות ב SQL וזה לא יתקרב לאפקטיביות של חיפוש פרצת SQL Injection בעצמך בקוד קיים וכתיבת Exploit. הידע התיאורטי על כל הסוגים של SQL Injection וסיפורי מקרה מעניינים ככל שיהיו מדברים לאנשים שיש להם אינטואיציה לגבי אבטחת מערכות, אבל האמת שאותם אנשים ממילא יגיעו למידע הזה. וכן יש לנו אותה בעיה עם AI. אנשים מקשיבים לשעות של הרצאות איך לקבל קוד טוב יותר מ AI אבל הבעיה היא לא ידע אלא אינטואיציה, ואינטואיציה מפתחים דרך עשייה - כלומר דרך אותו תהליך סיזיפי ואיטרטיבי של שיפור הקוד והפרומפט, קריאת הפלט של ה AI עד לקבלת התוצאה הרצויה. פה אתגר אחד הוא שהאדמה כל הזמן זזה וטכניקות שהיו נכונות לפני חודשיים כבר לא נכונות היום כי יש מודלים חדשים. אתגר שני הוא שכלים מודרניים עושים הכל כדי לעטוף את הפלט של המודל ולהרחיק אותנו מאותו פלט וכך אנחנו מאבדים את הקשר בין הפרומפט שלנו לתוצאה. עדיין מוקדם לדעת איך התחום הזה יתפתח. ומה לגבי קוד? לדעתי פה הבעיה פחות חמורה. גם יותר קל לראות כשקוד לא עובד וגם בעזרת ה AI יותר קל לחקור מקרי קצה או בעיות בקוד קיים. אם אני מסתכל קדימה בעולם שבו אנשים לא יכתבו קוד ידנית יותר ורק ישתמשו ב AI כדי לייצר אותו לא נראה ירידת מיומנות משמעותית שתתבטא באיכות המוצרים. באופן טבעי אנשים ימצאו את המקומות בקוד בהם יש לנו הכי הרבה השפעה ויתמקדו בהם וכך אנשים טכניים ומיומנים יבנו מוצרים טובים ומהירים יותר ממי שלא מבין והמוצרים שלא יעבדו יתמרצו את המפתחים שלהם ללמוד ולהשתפר. לסיכום האינטואיציה הבסיסית לגבי פיתוח מערכות וקידוד לא תישחק היא פשוט תשתנה. אנשים עדיין יעבדו בלהפעיל את סוכני ה AI ולהתווכח איתם והחיכוך הזה ישאיר אותנו בפוקוס. (וכן זה חיכוך. אם אני מבקש מסוכן קידוד למחוק את השינויים שהוא עשה והוא עונה לי "אין בעיה בוס אני רק משאיר פונקציה אחת כי היא ממש חשובה" אז זה מכריח אותי להכנס לקוד ולהבין מה הפונקציה הזאת כדי להחליט אם אני מבקש שוב או עוזב את זה). אינטואיציה לגבי אבטחת מידע אף פעם לא היתה הצד החזק שלנו ומעבר לפיתוח עם AI רק יחמיר את המצב הזה. פוסטים שאני קורא לאחרונה מחזקים את התחושה שאבטחת מידע לא קשורה למיומנות אלא לתקציב, ואם תתן ל AI מספיק טוקנים הוא ימצא בעיות אבטחה בכל מערכת. אם זה המצב לא ברור האם עדיין שווה להשקיע בהכשרת אבטחת מידע למפתחים בצורה גורפת בארגונים.

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

ToCode
1 423
📌 היום למדתי: strict loading ב Rails הנה פיצ'ר שנכנס בריילס 6.1, פספסתי אותו לגמרי כשהוא יצא ובסוף כשגיליתי עליו (בזכות ה AI) הבנתי למה העדפתי לחיות בלעדיו. לפני שנצלול לריילס יש פה לקח חשוב - כש AI מוסיף למערכת מנגנון שלא הכרתם, כדאי לקרוא לעומק על המנגנון ולחשוב אם הוא באמת רצוי או שמדובר במנגנון ישן שבאותו זמן שנוצר היתה התלהבות גדולה ממנו אבל אחרי כמה שנים גילינו שהוא לא היה כזה רעיון טוב. ✏ הבעיה עם טעינה עצלה בריילס מאוד אוהבים לטעון דברים רק כשצריך אותם לכן הקוד הזה:
Book.all
עדיין לא יטען את כל הספרים מבסיס הנתונים אלא הוא מייצג רק התחלה של שאילתה. אני יכול להמשיך אותו עם:
Book.all.first
ולקבל רק ספר אחד מבסיס הנתונים בלי שטענתי את כל הספרים לזכרון. זה רעיון מצוין עד שהוא פוגש מפתחים. כי הבעיה עם מפתחים היא שאנחנו יצירתיים וכותבים לולאות כאלה:
Book.all.each do |book|
    puts book.author.name
end
כיף, נוח, קריא ומאוד לא יעיל. הלולאה תריץ עבור כל ספר שאילתה לבסיס הנתונים כדי להבין את פרטי המחבר ותדפיס את שם המחבר. אם יש לי 10 ספרים זה אומר 11 שאילתות ובגלל זה לולאות כאלה נקראות N+1 Queries. שתי הדרכים המרכזיות לא לכתוב לולאות כאלה הן טעינה מראש או JOIN. עם טעינה מראש היינו כותבים:
Book.includes(:author).each do |book|
  puts book.author.name
end
ואז ריילס היה מריץ שתי שאילתות, אחת מושכת את כל נתוני הספרים והשניה מושכת את כל נתוני המחברים במכה אחת. דרך שניה היא JOIN והיא נראית כך:
Book.joins(:author).pluck("authors.name").each do |author_name|
  puts author_name
end
ופה יש לנו כבר שאילתה אחת שמחזירה רק את שמות המחברים. ✏ איך לזהות את התבנית השבורה הדרך הכי קלה לזהות את התבנית השבורה היא בזמן ריצה להסתכל על השאילתות. יש gem בריילס בשם bullet שעושה את זה ואפשר לקרוא עליו כאן: https://github.com/flyerhzm/bullet בעבודה עם AI הסוכן הציע להגדיר strict loading כדי לצמצם את הסכנה בתבנית זו:
Mitigation: the `Current`-scoped `localized_translation` association everywhere + `strict_loading` on `phrase_translations` / `token_translations` in development so a forgotten preload raises instead of silently N+1-ing.
אני מודה שלא הכרתי את המנגנון אז הלכתי לחפש, התחלתי עם המאמר הזה בנושא: https://thoughtbot.com/blog/strict-loading-in-rails-8-a-railsy-way-to-avoid-n-1-queries המשכתי לקצת Issues בגיטהאב ולשיחה עם ChatGPT. למדתי ש strict loading בגרסתו הראשונית נועד למנוע טעינה עצלה על יחסים מסוימים בריילס. לדוגמה ניתן להגדיר:
class Movie < ApplicationRecord
  has_many :reviews, strict_loading: true
end
ואז הקוד הזה יזרוק שגיאה במצב פיתוח:
puts Movie.first.reviews.count
והדרכים להגיע לביקורות יהיו להשתמש ב includes או join:
movie = Movie.first.includes(:reviews)
puts movie.reviews.count
הרעיון הזה באמת מונע את כל שגיאות ה N+1 אבל הוא מונע גם הרבה שימושים ממש תקינים במנגנון הטעינה העצלה של ריילס ולכן לא נרצה להשתמש בו. גרסה 7 של ריילס הוסיפה למנגנון מצב פעולה שנקרא n_plus_one_only שאמור לעזור לנו לזהות תבניות N+1 בלי לשבור המון קוד שאנחנו אוהבים. הבעיה שמנגנון זה לא הצליח לזהות בצורה אמינה את כל התבניות הבעייתיות וכאן יש דוגמה למקרה קלאסי שלא נתפס ברשת: https://github.com/rails/rails/issues/42576 בסוף אין ספק שטעינה עצלה מגיעה עם אתגר אמיתי בזיהוי N+1. אבל האתגר הזה היום הרבה פחות חמור ממה שהיה בעבר בגלל שסוכני קידוד ממש טובים בזיהוי תבניות ו N+1 כמעט תמיד מזוהה כתבנית בקוד המקור. מספיק להוסיף ל AGENTS.md ולפרומפט של ה Code Review שלנו הוראות מפורשות לזיהוי ותיקון שגיאות N+1 כדי לצמצם אותן משמעותית.

ToCode
1 423
פקודה זו לוקחת את כל הקבצים מענף new-design, מעתיקה אותם לענף main ומכניסה ל Staging בלי לקחת את הקומיטים. עכשיו נשאר לי רק לכתוב git commit ולנסח הודעת קומיט אחת שמתאימה לכל הפיצ'ר וכך אני זורק את כל היסטוריית הקומיטים של הענף ונשאר רק עם הגרסה החדשה ביותר שלו והודעת קומיט טובה. פקודת branch -d מוחקת את הענף ואפשר להמשיך בחיים:
$ git commit
$ git branch -d new-design
אפשרות שניה אם הפיתוח הוא גדול אולי אני כן רוצה לחלק לקומיטים אבל לפחות לסדר את הודעות הקומיט ואולי לצמצם חלק מהקומיטים. פקודת git rebase -i מציגה לי את כל הקומיטים בענף ומאפשרת לשנות טקסט של הודעות קומיט לא מושקעות או למחוק קומיטים שאין בהם צורך:
$ git switch new-design
$ git rebase -i main
בסיום התהליך תוכלו להפוך את הענף המבולגן עם 20 קומיטים לענף מסודר עם 3 קומיטים שמייצגים באמת נקודות מפתח שאתם רוצים לשמור בהיסטוריה המלאה. נ.ב. התוכנית המקורית היתה לדבר גם על git worktree בוובינר אבל לא הספקתי. במקום זה ביקשתי מה AI שיכין פוסט מסודר עליהם בסגנון של טוקוד. זה מה שיצא לדעתי זה נחמד חוץ מזה שצריך להוריד את המסמך כי בצפיה אונליין סדר המילים משובש בגלל העברית: https://chatgpt.com/share/6a59b2cf-ddcc-83eb-b176-49db4d3ecf37

ToCode
1 423
📌 סיכום וובינר: גיט וסוכני קידוד בוובינר השבוע דיברנו על גיט מנקודת המבט של עבודה עם סוכני קידוד. ראינו שתי תבניות עבודה פופולריות עם סוכנים, Spec Driven Development ו Step By Step Prompts ואיך גיט משתלב בכל אחת. בואו ניזכר מה היה שם. ✏ גיט ו Spec Driven Development שיטת Spec Driven Development אומרת שאנחנו בונים פרומפט אחד ארוך שנקרא Spec וממנו יוצרים את הקוד באמצעות סוכן. הפרומפטים נשמרים ונוכל לקרוא אותם בעתיד או כדי להיזכר מה המטרה של הקוד שנוצר או כדי לייצר מחדש את הקוד אם נרצה לראות פתרונות נוספים לאותן בעיות, למשל עם מודל אחר או עם שינויים קלים בפרומפט. בעבודת SDD אני מתחיל בכתיבת הפרומפט. בוובינר שמרנו את הפרומפטים בקובץ טקסט בתיקייה בשם spec בתוך הפרויקט. אחרי שהיה לנו פרומפט מוכן הכנסנו אותו לגיט:
$ git add spec/
$ git commit -m 'SPEC: new design'
ואז נתנו לסוכן לייצר את הקוד. אם הסוכן לא עשה עבודה מספיק טובה מחקנו את כל הקוד שהוא יצר:
$ git restore . && git clean -f -d
שיפרנו את קובץ הפרומפט, הכנסנו לגיט את הקובץ המתוקן:
$ git add spec/
$ git commit --amend
ואז הפעלנו שוב את הסוכן כדי לייצר את הקוד. בסוף כשהגענו לקוד שאנחנו מרוצים ממנו הכנסנו אותו עם הודעת קומיט מתאימה:
$ git add .
$ git commit -m 'IMPL: new design'
בסופו של דבר הריפו שלנו כלל זוגות של קומיטים, קומיט אחד של spec ואחריו קומיט של מימוש. באחד המימושים נתקלנו במכשול נפוץ - מתוך יצירת הקוד זיהינו שעלינו לעדכן משהו בסיסי יותר במערכת או קוד במקום אחר כדי שהמימוש של הפיצ'ר הנוכחי יעבוד טוב יותר. בשביל זה שמנו רגע "בצד" את הקומיט של ה spec באמצעות יצירת ענף בשבילו:
$ git branch new-design
פקודת git branch יצרה ענף חדש אבל השאירה אותי על main. אחרי זה לקחנו "צעד אחורה" וביטלנו את הקומיט של ה spec עם:
$ git reset --hard HEAD~
ואז יצרנו קובץ spec חדש ואת המימוש בשביל הפיצ'ר שהיה חסר, אחרי הקומיט החזרנו את הקומיט מהענף באמצעות ריבייס:
$ git switch new-design
$ git rebase main
$ git switch main
$ git merge new-design
$ git branch -d new-design
וכך קיבלנו:
<commit hash> SPEC: new design
<commit hash> IMPL: bugfix in webapp
<commit hash> SPEC: bugfix in webapp
<commit hash> IMPL: app start
<commit hash> SPEC: app start
ראינו שבעבודה בשיטת Spec Driven Design אנחנו יכולים תמיד לחזור לקומיט של SPEC, להדביק את קובץ הספק בסוכן קידוד ולקבל את הקוד שמתאים למימוש שלו. בגלל שזה תהליך שאפשר לחזור עליו אנחנו יכולים להתמקד בשיפור הפרומפט וגם בשיפור הפרויקט כדי שהסוכן יכתוב קוד טוב יותר. ✏ פיתוח Step By Step שיטת עבודה שניה שראינו היתה Step By Step Prompting. כאן אין לי פרומפט אחד גדול כי כשאני בונה את הפיצ'ר אני עדיין לא יודע איך הוא יראה. שיטת Step By Step מתאימה למקרים שאני מנסה דברים ולא יודע מה יהיה מבנה הקוד בסיום. לשיטה זו אני אוהב לפתוח ענף חדש כשמתחיל עבודה על הפיצ'ר וכל התקדמות תהיה קומיט באותו ענף, כלומר יהיה לי:
- prompt -> generate code -> commit
- prompt -> generate code -> commit
- prompt -> generate code -> commit
- prompt -> generate code -> commit
- prompt -> generate code -> commit
- prompt -> generate code -> commit
- prompt -> generate code -> commit
מבחינת גיט זה אומר שאנחנו מתחילים עבודה עם:
$ git switch -c new-design
ואז מייצרים קוד דרך פרומפט ומיד עושים קומיט. הודעות הקומיט בעבודה מסוג זה פחות מושקעות כי אני אפילו לא יודע אם הן יישארו בסוף, אני רק בניסויים. לפעמים ארצה לבטל עבודה של הסוכן או אפילו לקחת כמה צעדים אחורה, בשביל זה יש את reset:
$ git reset --hard HEAD~3
המספר 3 אומר כמה צעדים אחורה ללכת, ואפשר גם לכתוב מזהה קומיט. בסוף התהליך יש לנו קוד שאנחנו רוצים להכניס למערכת אבל הקומיטים גרועים, לא השקעתי בהודעות קומיט כמו שצריך ואולי קומיט ישן מכניס קוד שיימחק על ידי קומיט חדש יותר. הראיתי שתי טכניקות למיזוג הקוד חזרה לענף הראשי. הראשונה נקראת merge squash והיא אומרת שבעצם כל הענף הזה זה פיצ'ר אחד ולא צריך את ההפרדה לקומיטים, החלוקה לקומיטים היתה רק לצורך פיתוח כדי שיהיה לי קל לזוז אחורה וקדימה בקוד ש AI מייצר. בשביל שילוב merge squash אני עובר לענף הראשי, כותב:
$ git merge --squash new-design

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

ToCode
1 423
📌 קלוד קוד ותכנות הגנתי אחת הנטיות המובנות של מודלי שפה ביצירת קוד היא ליצור קוד מתגונן, כלומר קוד שבודק מספר אפשרויות לפני שמתייאש. קוד מתגונן היא תבנית טובה כשהקלט מגיע ממקור חיצוני שאין לנו שליטה עליו אבל הוא מיותר ומבלבל כשהמידע מגיע ממקום שאנחנו יצרנו. שימו לב לדוגמה הבאה בפייתון, קיצונית בכוונה:
data = {"x": 10}
if "x" in data:
    print(data["x"])
ברור שה if לא משנה בכלום את ריצת התוכנית אבל הוא מפריע לקרוא את הקוד ומעוות את הכוונה. האם כותבי הקוד התכוונו שאולי בהמשך המילון הזה יגיע מבחוץ? האם הקוד שמדפיס את x צריך לטפל בעוד מילונים? אנחנו לא יודעים. באופן כללי נרצה להשתמש בתכנות הגנתי כשיש ממה להתגונן ונרצה להיות ברורים לגבי המידע שיש לנו כשאנחנו יודעים בדיוק איזה מידע אמור להגיע לפונקציה. סוכני AI די גרועים בזה. ניקח דוגמה יותר מעניינת מפרויקט maigret. בקובץ report.py של הפרויקט אני מוצא שתי פונקציות: get_plaintext_report ו generate_report_context. הפונקציה generate_report_context מייצרת מילון עם נתונים והפונקציה get_plaintext_report לוקחת את הנתונים ובונה מהם דוח. בקוד יש באג - אם מחפשים משתמשים ששמם מכיל נקודה ואז רווח הדוח נשבר אחרי הרווח, כלומר משתמש כמו Dr. Evil יגרום בדוח להצגת הטקסט:
Search by username Dr.
Evil returned 0 accounts.
Extended info extracted from 0 accounts.
בגלל שבתוך המילון ש generate_report_context מייצרת יש בלוק של טקסט בו כל שורה מופרדת בנקודה ואז רווח ואז פונקציית יצירת הדוח מפעילה:
output = (context['brief'] + " ").replace('. ', '.\n')
כדי להדפיס את הדוח וזה גורם לשבירת השורה באמצע. הפיתרון קל ואפילו קלוד יודע מה לעשות כאן, צריך לשנות את generate_report_context כדי שתחזיר את השורות המקוריות של הדוח ואז לא נצטרך לשבור לשורות לפי סימן הנקודה. הבעיה שכשאני מבקש מקלוד לעשות את השינוי הוא מוסיף מפתח brief_lines וכותב את הפקודה ההגנתית:
if "brief_lines" in context:
    output = "\n".join(context['brief_lines'])
else:
    output = (context['brief'] + " ").replace('. ', '.\n')
זה מיותר כיוון שאנחנו יוצרים את המילון וברור שאם יש בו את brief יהיה בו גם את המפתח החדש brief_lines. הניסיון הראשון שלי לתקן את זה היה עם התוספת הבאה לקובץ CLAUDE.md:
Project Philosophy

- When we replace a mechanism in code don't keep the old one for record or backward compatibility
- Implement minimalistic solutions
- Consider what data is actually in a dictionary and use the correct key, don't cover too much ground
זה לא עבד. ניסיון שני היה להחליף את ה dict בחתימת הפונקציה ב TypedDict ולרשום ממש את כל סוגי המפתחות שיש בו. גם זה לא עבד. בניסיון השלישי הוספתי את הטקסט הבא ל CLAUDE.md ונראה שזה עובד:
Dictionary Defensive Programming

- When you read a key from a dict/object we construct ourselves, access it directly never .get() with a fallback. Fallbacks and defensive access are only for data crossing a trust boundary we don't control: user input, network responses, parsed files
וכן זה מאוד ספציפי למילונים. אבל אולי פה הקסם. בכל מקרה השורה התחתונה כאן מעבר לתכנות הדפנסיבי היא הגישה. בעבודה עם AI אין טעם רק לנסות לפתור בעיה בקוד אלא צריך להבין למה הבעיה נוצרה והכי טוב אם מצליחים לעדכן את הפרויקט כדי שהבעיה לא תיווצר במקרים דומים בעתיד.

ToCode
1 423
        result = await toolset.client.call_tool(
            "search",
            {field: "nice Spanish music", "language": "en"},
        )

    print(result.model_dump_json(indent=2, exclude_none=True))


if __name__ == "__main__":
    asyncio.run(main())
שרת MCP הוא בסך הכל API. הוא כבר נותן לכם את כל מה שצריך בשביל לבנות אוטומציות וחיבורים למערכת מתוך קוד. ה AI הוא רק בונוס.

ToCode
1 423
📌 איך בכל זאת התחברתי לשרת ה MCP של ספוטיפיי יש שני סוגים של שרתי MCP, שרתי MCP בתקשורת stdio שרצים אצלכם על המחשב ושרתי MCP שרצים על מכונה מרוחקת. אנטרופיק החליטו לבלבל את התעשייה עם סטנדרט חדש ושמות חדשים אבל האמת היא שמאחורי השמות מסתתרים אותם רכיבים שאנחנו מכירים: 1. שרת MCP מסוג stdio הוא בסך הכל כלי משורת הפקודה או סקריפט. 2. שרת MCP מסוג http הוא בסך הכל API. במילים אחרות, אם הורדתם שרת MCP מסוג stdio וחיברתם אותו לקלוד או ל VS Code אתם ממש לא חייבים לעבוד איתו רק דרך קלוד או VS Code ואפשר לעבוד איתו רגיל משורת הפקודה עם כלי תרגום כמו mcp2cli. זה באמת בסך הכל סקריפט שמקבל פרמטרים בצורה קצת יותר מסורבלת. מה שקצת מבלבל אנשים הוא ההזדהות - לקוחות MCP מנהלים בשבילנו את ההזדהות ולכן כשקלוד או VS Code מתחברים לשרת MCP הם שולחים אותנו לאינטרנט כדי להזדהות מול השירות שמתחברים אליו, לוקחים אליהם את הטוקן שחזר ושומרים אותו כדי להפעיל את הכלים. הפעלת כלים בשרת MCP בענן היא בסך הכל קריאת API. לפעמים שרתי MCP מנסים להרחיק אותנו וליצור מגבלות מלאכותיות. לדוגמה שרת ה MCP של ספוטיפיי עובד מצוין מתוך קלוד אבל כשניסיתי להתחבר אליו מקוד או מ mcp2cli הוא זרק אותי ואמר שהוא מוכן להתחבר רק ללקוחות מורשים. מעבר לקונקטור בקלוד או ChatGPT גם לא מצאתי אזכור רשמי לאותו שרת MCP של ספוטיפיי. מה עושים? קצת מחקר: 1. שאלתי את קלוד מה הכתובת של שרת ה MCP של ספוטיפיי אליה הוא מחובר. את זה לא היתה לו בעיה לספר לי. 2. שמתי לב שכשאני מתחבר לשרת ה MCP של ספוטיפיי הדפדפן מראה כתובת ארוכה שכוללת משתנים כמו redirect url ו client id. רשמתי אותם בצד. 3. שמתי לב שאחרי אישור הגישה לספוטיפיי הדפדפן שלי עובר בבקשת POST לקלוד שלוקח את הקוד שהתקבל ומשתמש בו כדי לקבל אסימון גישה, כל זה מאחורי הקלעים. אז חסמתי את הגישה ל claude באמצעות קובץ /etc/hosts כדי שלא יקבל את האסימון ולקחתי את הכתובת הזאת אליי, גם אותה רשמתי בצד. עכשיו כבר היה לי את כל מה שהייתי צריך כדי להתחזות לקלוד ולייצר אסימון גישה ל MCP של ספוטיפיי. ביקשתי מקלוד לכתוב סקריפט פייתון שמייצר פרמטרים למסך ההרשאות של ספוטיפיי ומחכה שאני אדביק את כתובת החזרה כדי לקחת משם את הפרמטרים של התשובה, מחבר את הכל לאסימון ואז מחפש קצת מוזיקה בספרדית. זאת התוצאה:
import asyncio
import os
from urllib.parse import parse_qs, parse_qsl, urlencode, urlparse, urlunparse

from dotenv import load_dotenv
from fastmcp.client.auth import OAuth
from pydantic import AnyHttpUrl
from pydantic_ai.mcp import MCPToolset


MCP_URL = "https://mcp-gateway-external-pilot.spotify.net/mcp"
REDIRECT_URI = "https://claude.ai/api/mcp/auth_callback"


class ConsoleOAuth(OAuth):
    def __init__(self, client_id: str) -> None:
        super().__init__(
            mcp_url=MCP_URL,
            client_id=client_id,
            callback_port=1,
            callback_timeout=600,
        )
        redirect_uris = [AnyHttpUrl(REDIRECT_URI)]
        self.context.client_metadata.redirect_uris = redirect_uris
        if self._static_client_info is not None:
            self._static_client_info.redirect_uris = redirect_uris

    async def redirect_handler(self, authorization_url: str) -> None:
        parsed = urlparse(authorization_url)
        query = dict(parse_qsl(parsed.query))
        query["show_dialog"] = "true"
        print(urlunparse(parsed._replace(query=urlencode(query))))

    async def callback_handler(self) -> tuple[str, str | None]:
        callback_url = await asyncio.to_thread(input, "Paste callback URL: ")
        query = parse_qs(urlparse(callback_url).query)
        code = query.get("code", [None])[0]
        state = query.get("state", [None])[0]
        if not code:
            raise RuntimeError("Callback URL does not contain an OAuth code")
        return code, state


async def main() -> None:
    load_dotenv()
    client_id = "a0894f3786c34e64b0fa0e77f8ea83db"
    toolset = MCPToolset(
        MCP_URL,
        auth=ConsoleOAuth(client_id),
        init_timeout=600,
    )

    async with toolset:
        tools = await toolset.list_tools()
        search = next(tool for tool in tools if tool.name == "search")
        field = "query" if "query" in search.inputSchema["properties"] else "prompt"

ToCode
1 423
📌 השלכות של פיתוח עם AI על פרויקטים בפרויקט אחד מצאתי שני API Clients שונים שמחברים לאותו ספק צד-שלישי. ביצירת הפיצול סוכן ה AI כתב הערה ארוכה שמסבירה למה חלק מהפונקציות צריכות לעבור דרך מנגנון אחד ופונקציות אחרות דרך מנגנון שני. משם אף סוכן AI לא העלה בדעתו לבדוק מחדש ולאחד את המנגנונים. בפרויקט אחר מצאתי שני קבצי CSS סותרים - העיצוב החדש והעיצוב הישן. שניהם הופעלו על העמוד אבל רק העיצוב החדש הופיע והוא דרס את כל הכללים של העיצוב הישן. במקום אחר אותה פונקציה הופיעה כמה פעמים בשינויים קלים. רוב הזמן זה לא הפריע לאף אחד כי כל חלק בקוד קרא למימוש הספציפי שהתאים לו. עד שמישהו שינה את הפורמט ו-5 מקומות לא קשורים נשברו. בדוגמה רביעית קיבלתי פונקציה עם הערה שלא תאמה את מה שמבוצע בפונקציה. קלוד שינה את הפונקציה אחרי הכתיבה בלי לעדכן את ההערה. המשך שינויים לפעמים בוצעו לפי קוד הפונקציה ולפעמים לפי ההערה, מה שרק הרחיב את אי התאימות. בפרויקט חמישי ה AI פיתח מאפס מנגנון שקורא HTML ומוציא ממנו את החלקים החשובים בטקסט במקום להשתמש בספריה קיימת כמו readability של מוזילה או trafilatura. פרגמנטציה בקוד המיוצר על ידי AI היא עדיין בעיה מרכזית בכל פרויקט. הסוכנים לא עושים מספיק בשביל לאחד מנגנונים או אפילו בשביל לאתר מנגנונים קיימים במערכת לפני שרצים לממש מחדש. שימו לב בעת קריאת קוד שנוצר על ידי AI לזהות כאלה פיצולים, לצמצם אותם וגם להכניס לקבצי AGENTS.md הסבר על המנגנון הקיים, דרישה להשתמש בו ודרישה שכשמשנים מנגנון לא להשאיר את השיטה הישנה.