חמש תשובות על שילוב AI בפיתוח
דה מרקר פרסמו כתבה גדולה על המצב של התעשייה בעקבות מהפכת ה AI וחלק משמעותי מוקדש שם לפיתוח. אני רוצה להתיחס ל-5 ציטוטים שעלו משם בעיקר כדי להוסיף צבע וקונטקסט כי הסיפור הרבה פחות ברור ממה שהוצג.
טיפים קצרים וחדשות למתכנתים
דה מרקר פרסמו כתבה גדולה על המצב של התעשייה בעקבות מהפכת ה AI וחלק משמעותי מוקדש שם לפיתוח. אני רוצה להתיחס ל-5 ציטוטים שעלו משם בעיקר כדי להוסיף צבע וקונטקסט כי הסיפור הרבה פחות ברור ממה שהוצג.
לאחרונה ראסט הכריזו על מדיניות LLM שסוף סוף עוזבת את התבנית הקבועה של "אין לנו בעיה עם LLM" או "אין כניסה לסוכנים". אנחנו נוטים לחשוב ש"מדיניות LLM" זו בעיה של פרויקטי קוד פתוח שמקבלים קוד מן הגורן ומן היקב, אבל האמת שרוב חברות התוכנה שראיתי רק ירוויחו מהגדרת מדיניות ברורה לשימוש ב LLM מעבר ל"תבזבזו כמה שיותר טוקנים אבל לא מעבר לתקציב".
אלה הדברים המרכזיים שמדיניות LLM צריכה להגדיר:
גם אם החלטתם שסוכני קידוד הם כלי עבודה שאי אפשר בלעדיו ו 100% מהקוד צריך להיות מיוצר על ידי סוכנים יש עוד ערימה של דברים שצוותי פיתוח מייצרים: אימיילים, טיקטים בג'ירה, דיווחי באגים, תשובות למשתמשים, סיכומי פגישות, תיעוד, מדריכים למפתחים חדשים. אם LLM יכול להכניס לבד דיווחי באגים אז יהיו דיווחי באגים מיותרים. אם LLM הולך לכתוב טקסט של טיקטים אז טיקטים יהיו לא עקביים ואולי לא מדויקים. כלל אצבע במדיניות של ראסט אומר למשל:
Showing LLM output to another human without solicitation is likely banned
ברור שאי אפשר לאכוף את זה וזו לא המטרה. הסיפור כאן הוא תיאום ציפיות והוגנות. אני הייתי מוסיף שאם אתה רוצה להדביק פלט של LLM לשיחה עדיף שתדביק את הפרומפט שכתבת לו. לכולם יש גישה לקלוד ויכולים לייצר בעצמם את ההשלמה.
לא משנה מי הסוכן שכותב את הקוד יש בעיות והטיות מובנות לכל הכלים האלה: הם כותבים קוד מתגונן, הם לא מוחקים, הם כותבים בדיקות ספציפיות מדי, הם כותבים טלאים שגורמים לדברים לעבוד גם כשהם לא נכונים, הם כותבים קוד משוכפל בכמה מקומות ורוב הזמן מתקשים למצוא את המקום הנכון לכתוב את המימושים שלהם.
אפשר להחליט שאחרי כל יצירת קוד עם סוכן קידוד מישהו אנושי נכנס לקוד ומתקן. זה לוקח זמן ודורש מיומנות כלומר במקום שפיצ'ר יהיה באוויר אחרי שעה ש LLM ישב עליו הוא יצטרך יומיים של מפתח אנושי. אפשר להחליט שאנחנו דוחפים כל מה ש LLM יצר אם זה לא ממש שגוי ומשקיעים זמן קבוע פעם בשבוע בניקוי הקוד כולל קוד ישן. אפשר להחליט שאנחנו מתקנים כשמזהים באגים תוך כדי תנועה. אפשר להחליט שאנחנו רצים הכי מהר שאפשר עד שהמכונה נתקעת ואז עוצרים לנקות או מתחילים פרויקט חדש. מה שלא תחליטו כדאי שכל האנשים בארגון יהיו מיושרים על ההחלטה. אם מנהל הפיתוח בטוח שכל קוד שנכנס למערכת עבר Review אנושי ואיטרציות והוא עומד בסטנדרטים הכי גבוהים של פיתוח אבל בצד השני יש מפתחת שבטוחה שהדבר הכי חשוב זה מהירות ואסור לבזבז זמן על קריאת הקוד מתישהו יהיה פיצוץ.
אם חבר מבקש שתעברו על קוד שהוא כתב זה בסדר להשתמש ב AI? אם צריך לתקן בעיה בגיט זה בסדר לתת לקלוד לסדר את זה? מה לגבי ניתוח פלט של בדיקות ותיקון בדיקות שנכשלו? אל תתנו לכל מפתח להחליט לבד רמת המיומנות שלו או שלה או לפי כמה זמן יש להם עכשיו. בואו ניישר קו - אצלנו לפני שקוד נכנס לפרודקשן הוא עובר Review של בודק ה AI, או אצלנו לפני שגרסה עולה לפרודקשן מפתח אנושי חייב לשבת יומיים ולקרוא את כל הקוד שהשתנה מאז הגרסה האחרונה, או אצלנו מפתח אנושי חייב לעבור על הפלט של בודק ה AI. מה שתחליטו זה בסדר רק תראו שאתם שלמים עם ההשלכות.
למרות שמודלי שפה לא חותמים על הטקסט שהם מייצרים לבני אדם במיוחד בארץ קל מאוד להבין כשאנחנו מדברים עם מודל שפה בשונה משיחה עם בן אדם אמיתי. קל לנו מאוד לזהות פלט של מודל שפה ואפילו קוד של סוכן קידוד במיוחד על מערכת גדולה. המכונות האלה אולי מייצרות טקסט דומה לזה שבני אדם מייצרים אבל הן לא בני אדם. חלק חשוב מהיכולת לעבוד עם מכונות אלה בצורה יעילה הוא חלוקת התפקידים שתתאים ליכולות, למגבלות ולאחריות של כל אחד.
ריבייס מאפשר לייצר היסטוריית גיט לינארית באמצעות יצירה מחדש של הקומיטים שבוצעו לפיצ'ר מסוים על גבי קומיט חדש יותר. זה עובד ממש טוב כשאני בונה פיצ'ר ובזמן הזה הענף הראשי התקדם. אני רוצה לשלב את הפיצ'ר במערכת אבל ההתקדמות של הענף הראשי היא לא הסיפור, ולכן אני עושה rebase מהענף שפיתחתי על הקומיט החדש ביותר בענף הראשי, מה שגורם לגיט "לבנות מחדש" את פיתוח הפיצ'ר מתוך הקומיט החדש ביותר. אחרי ריבייס אפשר למזג ובהיסטוריית גיט זה נראה כאילו חיכיתי לסיום הפיצ'רים של הענף הראשי לפני שפיתחתי את הפיצ'ר שלי, כלומר הפיצ'ר שלי נבנה על גבי הפיצ'רים מהענף הראשי.
עכשיו בואו נשבור את זה.
נניח שבשלב מסוים main התקדם ואני רציתי בפיצ'ר שלי לקבל שינויים שנכנסו ב main אז ביצעתי merge מ main ל feature1. המיזוג לא עבר חלק ופתרתי קונפליקטים אבל זה בסדר כי ממילא הייתי צריך לפתור את הקונפליקטים האלה לפני שאני יכול לשלב. בינתיים main ממשיך להתקדם וכעבור זמן אני מגיע לריפו במצב הבא:
* 45cfa5d (main) main: 3 commits ahead
* 51c18b4 main: 2 commits ahead
* dfad935 main: 1 commit ahead
| * fa37287 (HEAD -> merge-feature1) Merge feature1 into old main (RESOLVED CONFLICT)
|/|
| * 2392145 (feature1) feature1: add HTML report part 2
| * 6b24764 feature1: add HTML report part 1
* | 9961b60 main: added some changes
|/
* d4d0727 fixed stuff
* 9155d8f initial commit
מה יקרה אם עכשיו אני מנסה להפעיל את אותו טריק ולבצע ריבייס מהענף הממוזג ל main החדש? שימו לב:
(merge-feature1) $ git rebase main
Auto-merging main.py
CONFLICT (content): Merge conflict in main.py
error: could not apply 6b24764... feature1: add HTML report part 1
hint: Resolve all conflicts manually, mark them as resolved with
hint: "git add/rm <conflicted_files>", then run "git rebase --continue".
hint: You can instead skip this commit: run "git rebase --skip".
hint: To abort and get back to the state before "git rebase", run "git rebase --abort".
hint: Disable this message with "git config set advice.mergeConflict false"
Recorded preimage for 'main.py'
Could not apply 6b24764... # feature1: add HTML report part 1
קיבלתי קונפליקט על קומיט 6b24764, כלומר קונפליקט שכבר פתרתי ב Merge Commit. היה לי רעיון טוב להקדים תרופה למכה ולמזג את השינויים מ main לפני שאני מסיים לעבוד על הבראנץ' אבל בסוף זו היתה עבודה מיותרת כי ריבייס מתעלם מהקונפליקטים שפתרתי.
מה עושים? קודם כל כדאי לזכור שקשה להזיז merge commit ולכן עדיף לחשוב לפני שאתם מתחילים merge איפה אתם רוצים שה merge commit שלכם ישב. אם בכל זאת תבקשו מאיזה קלוד להתעקש הוא ימזג חזרה את main העדכני לתוך merge-feature1, ימחק את main ואז יבנה קומיט חדש עם git write-tree שיש לו רגל אחת על אותו ענף צד ורגל שניה איפה שהיה פעם main.
כשאתם נותנים ל AI לכתוב Shell Scripts שמחפשים בקבצים הוא אוהב להשתמש ב grep. זה עובד בשמות פשוטים ויכול לבלבל כשהשמות מכילים תווים מיוחדים של ביטויים רגולארים.
לדוגמה נתון קובץ טקסט עם השורות הבאות:
line1
line2
line3
line4
line.txt
בשביל לבדוק אם קיימת בו השורה line3 נוכל לכתוב:
$ grep line3 demo.txt
ונקבל את השורה.
בשביל לבדוק אם קיימת בו השורה line.txt מפתה לכתוב גם:
$ grep line.txt demo.txt
וזה גם עובד ומדפיס את line.txt - אבל זה בטעות. הנקודה ב line.txt היא תו של ביטויים רגולאריים ובעצם מתאימה לכל תו. לדוגמה אם הקובץ שלנו מכיל:
line-txt
line_txt
line2txt
אותה פקודת grep תחזיר את כל השורות:
grep line.txt demo.txt
line.txt
line-txt
line_txt
line2txt
מה עושים? נזכרים בטיפ בכותרת. פקודת grep -F או פקודת fgrep מחפשות טקסט פשוט ולא ביטוי רגולארי לכן כשאנחנו מחפשים טקסט רגיל נעדיף לכתוב:
$ fgrep line.txt demo.txt
line.txt
ונקבל רק את השורה עם הנקודה ולא את השורות שמתאימות לביטוי הרגולארי.
נ.ב. grep עצמו מבלבל כי הוא מתעלם באופן אוטומטי מתווים מסוימים של ביטויים רגולאריים במיוחד פלוס, כלומר אם בקובץ יש לי את השורות:
2+4
2229
פקודת grep תחזיר ממש את הפלוס ותתעלם מהפלוס במשמעותו המיוחדת של "תו נוסף" שאנחנו מכירים מביטויים רגולאריים:
$ grep '2+' demo.txt
2+4
אבל זו רק הטעיה כי אם מוסיפים לה \ לפני הפלוס היא חוזרת להתיחס אליו כמו תו של ביטויים רגולאריים:
$ grep '2\+' demo.txt
2+4
2229
המדרגה הבאה של תכנות נראית פחות ופחות כמו קוד ובאותו זמן יותר ויותר טכנית.
כל החלטה טכנית שאנחנו מקבלים בזמן הפיתוח או שאנחנו משאירים ל AI לקבל קובעת מה יצליח, מה לא יצליח וכמה זמן דברים יקחו. הערכת זמנים הפכה למשחק של ידע, לשאלה הפשוטה "האם פיצ'ר X מתאים למבנה של המערכת".
אם המערכת שלי צריכה למלא טפסים למשתמשים, האם אני מייבא את הטפסים לפורמט פנימי שלי, ממיר אותם ל HTML או כותב ישירות על ה PDF? כל בחירה מגיעה עם יתרונות וחסרונות שלה. אני אולי לא צריך יותר לדעת איך נקראת הפונקציה שכותבת על PDF, אבל כדאי מאוד שאני אדע שבשביל לכתוב על PDF צריך קואורדינטות, ובשביל לכתוב על HTML צריך לקרוא מבנה של עץ.
ב Langlets בניתי סוכן AI שמייצר תרגילי אוצר מילים בשפה. ההחלטות על היכולות של הסוכן - האם הוא מייצר HTML מלא? האם הוא מייצר אפליקציה מלאה (כולל צד שרת)? האם הוא מייצר את התרגיל בפורמט פנימי שלי, איך יישמרו התרגילים ואיך יישמרו התשובות, כל אלה קובעות מה אפשר יהיה לבצע בתרגילים. ההחלטות יוצרות את המסגרת, הסוכן מצייר בתוך הקווים.
כשאני מחליט לפתור בעיה בדרך מסוימת יש דברים מסוימים שיהיו קלים ואחרים שיהיו קשים. וזה נכון בהחלטות גדולות וקטנות, כשאני בוחר לשמור מידע מסוים בבסיס נתונים, ב Cookie, בפרמטר של ה URL. כשאני בוחר באיזה אירועים לטפל. אין בעיה להוסיף לדוח עמודה שמציגה ערך ששמור בבסיס הנתונים בצורה נכונה. אבל אם המידע לא שמור או לא שמור נכון אנחנו בבעיה.
בתוך ערימה של קוד יש תמיד מספר קטן של נקודות החלטה או נקודות כשל. נקודות שקובעות מה יהיה קל ומה יהיה קשה בהמשך. הן קשורות לקוד ושמורות בו, אבל הן לא הקוד. הקוד שכותב לקובץ כולל את ההחלטה לשמור מידע לקובץ. הקוד שמושך מידע מבסיס נתונים עם SQL Window Function כולל בתוכו את ההחלטה לקרוא ולכתוב את המידע בצורה מסוימת בבסיס הנתונים.
דוגמה קטנה מ Langlets להמחשה - יצרתי פעילות של סידור מילים במשפט, לדוגמה
ביקשתי מה AI לעדכן את הקוד שבונה קורס כדי שישתמש בפעילות הזאת רק במשפטים שלא עוברים את העשר מילים, אחרת זה ארוך מדי ולא כיף. אז הוא כתב קוד כזה:
def word_order_phrases(phrases)
phrase_list = phrases.is_a?(ActiveRecord::Relation) ? phrases.to_a : phrases
phrase_list.select do |phrase|
words = phrase.text_l1.to_s.split(/\s+/).reject(&:empty?)
words.any? && words.size <= MAX_WORD_ORDER_WORDS
end
end
כדי להחזיר את המשפטים האפשריים. הקוד כולל בעיות לא חשובות, למשל גם למערך ברובי יש פונקציית to_a ולכן אם החלטת שאתה ממש חייב מערך אתה יכול פשוט תמיד לקרוא ל to_a ואם זה מערך ממילא זה יחזיר את עצמו. אבל יש פה גם החלטות ממש חשובות וטפשיות שה AI לקח:
הבחירה לכתוב את הפונקציה הזאת בקובץ שבונה את הקורס במקום בקובץ של פעילות סידור המילים אומרת שיהיה קשה לזהות אותה ולהשתמש בה במקומות אחרים במערכת.
יותר חשוב הבחירה לממש חלוקה למילים בתוך הקוד בתור split לפי רווחים היא מאוד נאיבית, לא נכונה ולא מתאימה לאתגרים של המערכת. מה שמעניין אותנו זה המילים שצריך לסדר בפעילות ולא כמה מילים יש במשפט וכשיש צירוף מילים כמו The United States למרות שיש בו רווחים אנחנו מסתכלים עליו כמילה אחת.
מצד אחד לא צריך לדעת לכתוב לולאות ב Ruby ולא צריך לזכור אם זה filter או select או reject ואם צריך לבדוק שמערך גדול מאפס או שיש פונקציה בשם any? שמחזירה את זה ואפילו לא יקרה כלום אם נדלג על ה to_a או התנאי בשורה הראשונה. אבל פיצול שורה למילים לפי רווחים כבר ישבור את ההגיון הפנימי של המערכת וזו החלטה לא נכונה. גם אם זה יעבוד היום זה מהר מאוד יישבר.
(ושלא תתחילו עם "פשוט תכניס את כל החוכמה הזאת ל AGENTS.md. הגיון של החלטות משתנה ויש כל הזמן החלטות חדשות. ברור שצריך להשתמש ב AGENTS.md אבל הוא לא מחליף את קריאת הקוד).
קלוד קוד מנסה לחשוף את ההחלטות אבל זה לא הצד החזק שלו. קשה לדעת אם בעתיד זה יתאפשר. שפות תכנות עתידיות אולי יוכלו לתת מענה טוב יותר באמצעות הפרדה בין הקוד להחלטות מאחוריו. נחכה ונראה. בינתיים כדאי להתרגל לזהות נקודות כשל. נקודות של איזון בהן הבחירה קובעת את ההמשך ולהתרגל לראות את העתיד מתוך ההחלטות באותם צמתים.
המדרגה הבאה של תכנות היא לא כתיבת קוד. היא קבלת החלטות טכניות על המבנה הפנימי של המערכת. האילוץ הוא כבר לא כמה מהר אפשר לכתוב את הקוד אלא העולם האמיתי. האם הקוד שרצית לכתוב יכול לפתור את הבעיה שאתה צריך לפתור.
אם אני ממש טוב ראיתי תוך רגע ש AI החליט החלטות לא הגיוניות ושלחתי אותו לסיבוב תיקונים לפני שמיזגתי. ככל שסוכני קידוד ומודלים משתפרים מתכנתים שמחפשים בעיות בקוד AI עלולים למצוא את עצמם מתוסכלים. אתה קורא את הפרומפט והקוד, הכל נראה הגיוני, ואתה מרגיש חסר תועלת.
אבל כשהקוד החדש נראה מושלם אני אוהב להזיז את המבט לקוד הישן שבמערכת. הרבה פעמים קוד חדש חושף בעיות ואי דיוקים בקוד ש AI כתב לפני שבוע, חודש או יותר.
כשהוא מתחיל להסביר לי על התיקון שהוא בנה שמפענח טוב יותר את ההודעות שחוזרות מהסוכן אני שואל "ולמה לא השתמשנו לפני כן ב Structured Output"?
כשהוא מדבר בשבחי מנגנון ה Cache שהוא הוסיף עכשיו כדי שהמידע יחזור מהשרת מהר יותר אני שואל "אבל למה השאילתה עד עכשיו לקחה כל כך הרבה זמן"?
כשאני מבקש להחליף טקסט של כותרת וה AI מספר לי שהוא הגדיל ראש ומצא את הטקסט הזה בעוד 3 מקומות ותיקן את כולם אני ארצה לשאול "למה זה היה שמור בשלושה מקומות"?
כל תיקון כשלעצמו אולי הכי מדויק שיכול להיות - אבל במערכת טובה זו התמונה המלאה שקובעת.
כל יום עוד סלב משתף בלינקדאין על איזה אפליקציית AI שהוא או היא כתבו בכמה ימים רק עם קלוד קוד "והכל עובד תוך רגע". אני לא ראיתי את המערכות שלהם או את תהליך הפיתוח אבל בהתבסס על החוויה שלי מעבודה עם AI אני די סקפטי. אלה הדברים המרכזיים שעדיין לוקחים לי המון זמן בפיתוח ואני לא רואה איך הם נפתרים על ידי "מודלים יותר טובים" או "יותר סוכנים" או כל דבר מהסוג הזה.
מצד אחד AI הוא כלי נהדר למפתחים שרוצים ללמוד יותר לעומק איך דברים עובדים ולחקור רעיונות. זו מכונת רעיונות שגם יודעת לממש ומראה לי איך כל רעיון בא לידי ביטוי בקוד. מפתחים שרוצים להשקיע יכולים להגיע לשיחות הרבה יותר מוכנים - לא רק עם רעיון אלא גם עם מימוש עובד שלו.
ומצד שני החיים האמיתיים.
התחברתי מאוד להערה הזאת שמישהו השאיר בהאקרניוז בהמשך לסיפור על חיפוש באגים ושיפורי ביצועים:
Not only did I get sidetracked by a bunch of useless suggestions but I also had to put up with others dumping their raw AI output at me as if it was somehow a meaningful contribution.
גם בעבודה שלי, לפחות 30% מהפלט שאני מקבל מ AI לא ראוי לשמירה. אין לי ספק שבשנים הקרובות ואולי עשורים הקרובים האחוזים האלה יצטרכו לרדת ושאנחנו רק בתחילת הדרך, אבל נכון להיום עם פחת של 30% אני ממש לא רוצה לשמוע מה קלוד אמר לכם.
"אי אפשר להחליף מטכנולוגיה X ל Y" - ואז הסבר ארוך מודבק מ AI.
"יש בעיית אבטחה אם עושים X" - ואז הסבר ארוך מודבק מ AI.
"יהיה יותר נכון להוסיף redis" - ואז הסבר ארוך מודבק מ AI.
אם אתם מסוגלים להסביר את הטיעון במילים פשוטות שלכם ובאים עם הוכחות ודוגמה עובדת אני אשמח להקשיב. קופי פייסט מ AI לא מוסיף לדיון.
בוובינר אתמול דיברנו על פיתוח בדיקות אוטומטיות בעזרת AI. אלה הטכניקות המרכזיות שראינו.
זה נראה כמו לפני נצח אבל האמת שרק לפני כמה חודשים האינטרנט היה מלא בטיפים לכתיבת פרומפטים טובים יותר. "תגיד ל AI שהוא מתכנת על", "תזכירו לו לחשוב צעד-צעד", "כתבו את הדברים החשובים בסוף", "כתבו את הדברים החשובים בהתחלה".
הטיפים האלה, למרות שהיו נכונים לאותה תקופה, היו בסך הכל הסחת דעת מהמיומנות האמיתית בעבודה עם AI. הם התיישנו מהר וההתיישנות שלהם צריכה ללמד אותנו לקח - מיומנות בעבודה עם AI זה לא אוסף של כללי "עשה" ו"אל תעשה". זאת קודם כל גישה ושיטת מחשבה.
ויש רק שני דברים שחשוב לשפר כאן:
הנדסת תוכנה - בשביל ש AI יוכל לייצר את הקוד שאני רוצה, אני צריך לדעת איזה קוד אני רוצה. הבנה טובה של עולם התוכן, ארכיטקטורה, אפשרויות ומה יכול להישבר היא הכרחית כדי לקבל תוצאה טובה מ AI. אתם רוצים להיות במקום שכש AI מייצר קוד אתם קוראים את הקוד הזה ויכולים להגיד "זאת לא הגישה הנכונה" או "זאת לא המערכת שרציתי".
הנדסת התהליך - מה הבעיות בקוד ש AI יצר למערכת שלי? מה גרם ל AI לייצר את הבעיות האלה? מה אני יכול לעשות כדי לצמצם את הבעיות ולקבל קוד טוב יותר? הנדסת התהליך הולידה את הטיפים של Prompt Engineering, אבל הטיפים לא היו חשובים. מה שהיה חשוב היה הגילוי שלהם, כי כשבאו מודלים יותר טובים זרקנו את הטיפים אבל המשכנו לעבוד ולגלות איך לקבל תוצאות טובות יותר מהמודלים החדשים.
מהנדס תוכנה יגיד "אם במקום Inheritance תשתמש ב Delegation המערכת שלך תוכל להתמודד עם שינוי דרישות פוטנציאלי בצורה טובה יותר".
מהנדסת תהליכי AI תגיד "אם במקום לשמור את כל הפונקציות בקובץ אחד תפצל אותן למספר קבצים הסוכן יוכל לקרוא את הקוד בפחות טוקנים ולתת לך תוצאות יותר טובות" או "אם תשים את כל הקוד שקשור לתהליך מסוים באותו קלאס הסוכן יוכל להבין את התהליך מהר יותר ולתקן בעיות בו בצורה יעילה יותר", או "אם נבחר סט קטן יותר אבל מייצג של בדיקות אוטומטיות שיכול לרוץ ב-30 שניות נוכל לבקש מהסוכן להריץ את הבדיקות אחרי כל שינוי במקום לשבור קוד קיים".
פרומפט אנג'ינירינג אולי התיישן מהר. הטכניקה שהולידה אותו תישאר איתנו להרבה זמן.