ch
Feedback
ToCode

ToCode

前往频道在 Telegram

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

显示更多
1 419
订阅者
-124 小时
无数据7
-430
帖子存档
ToCode
1 419
שאלות ש AI לא יודע לענות עליהן למה שמת את כל הקוד בקובץ אחד? למה בחרת את ה API הישן יותר? למה כתבת מחדש את הפונקציה שכבר היתה כתובה במקום אחר (במקום להפוך אותה מ private ל public)? למה שמרת את החיבור ל DB במשתנה גלובאלי ושכחת לאפס אותו וכך יצרת לי זליגת זכרון? למה מחקת לי את הקובץ? למה אתה מתעקש ליצור אוביקט בלולאה כשאני מתחנן שתכתוב Obect Literal? למה לא יצרת אינדקסים על הטבלאות שיצרת ב DB? למה יצרת קובץ locale חדש אפילו שכבר יש קובץ locale באפליקציה לכל הפיצ'רים האחרים? למה בפונקציה אחת כתבת
setIsFlipped(!isFlipped);
if (!isFlipped) {
  setShowRating(true);
}
אבל שתי שורות למטה כתבת
const handleRate = (rating: number) => {
  onRate(rating);
  setIsFlipped(false);
  setShowRating(false);
};
(או שצריך if או שלא צריך) למה בכפתור אחד כתבת stopPropagation אבל בכפתור השני לא? למה אתה מושך מידע מהשרת עם useEffect ולא משתמש באיזה react-query ? למה אתה כותב קוד שאפילו אתה לא תוכל לתחזק?? --- וואו מדהים ששמת לב! איזה חד אבחנה אתה. באמת הייתי צריך להשתמש במנגנון שאתה מציע. הנה מיד אני מתקן. --- לכל מי שחושב ש AI הולך להחליף מפתחים Any time soon אני יכול רק להציע - שבו איתו (או אתה, זה גמיש) כמה ימים. תנו להם לכתוב קוד ותקראו את מה שיוצא. נכון, אין טעויות syntax אבל קבלת החלטות זה לא הצד החזק שלהם.

ToCode
1 419
טיפ גיט: הצילו מחקתי את תיקוני הקונפליקט מחקתם תיקוני קונפליקט שעבדתם עליהם שעתיים? לא צריך לדאוג אנחנו בגיט. בואו נראה איך לשחזר אותם. אני יוצר פרויקט עם שלושה קומיטים בשלושה ענפים, ענף main, ענף b וענף c:
* fb6d328 (HEAD -> c) c
| * fc29558 (b) b
|/
* c3482e6 (main) a
עכשיו הולכים לענף c וממזגים את ענף b. מה מקבלים? נכון, קונפליקט. אם גם b וגם c שינו את אותו קובץ באותו מקום גיט לא יצליח למזג את הקונפליקט לבד ויציג לנו קובץ עם סימני קונפליקט לטיפולנו, למשל:
<<<<<<< HEAD
**The Clockmaker’s friend**
=======
**The Clockmaker’s bug**
>>>>>>> b
ענף c שנקרא HEAD מכיל את המחרוזת העליונה וענף b שאותו ניסיתי למזג מכיל את המחרוזת התחתונה ואני צריך לבחור. עד פה אין חדש. אנחנו גם יודעים לסדר את הקונפליקט, לעשות git add ואז קומיט שוב וליצור Merge Commit. אבל מה קורה אם אחרי ה add התבלבלתם והרצתם:
$ git merge --abort
פקודת merge abort מבטלת את המיזוג ומוחקת את כל השינויים המקומיים כך שהעולם חוזר להיות כמו לפני המיזוג. אבל רגע - מה קרה לתיקונים שלי? מה קרה לקונפליקט שפתרתי? הקובץ שהיה בקונפליקט עכשיו חזר להיות כמו בענף c לפני המיזוג. האם המאמץ שלי בפתרון הקונפליקט ירד לטמיון? האם אפשר לשחזר את הקובץ או שחייבים לפתור את הקונפליקט שוב? נו בגיט כמו בגיט מרגע שעשינו add הדבר נכנס לריפו גם אם לא עשינו קומיט ולנו נשאר רק למצוא אותו. אני מריץ:
git fsck --cache --no-reflogs --lost-found --unreachable HEAD
ומקבל רשימה של שטויות שהלכו לאיבוד:
unreachable tree 606ee30389b8548213f2f11596605040a54fe4e0
unreachable tree 72c93914a344c812fcf5fa7216cfd1e464bd9cc3
unreachable tree f85d50eb6572db3613432d0608b5338120883acb
unreachable blob 5c7cfc2892118a24ebbc3f8511010899e6d09781
unreachable blob dd0221c310613ea89104ce0fede8f10bf6df626a
unreachable blob 1be3a6878600782668ffaf33ff9b8843078a06cd
unreachable commit fc295586d6e98fea615ebec979850bd13b8441c3
יחסית מעט כי זה פרויקט דוגמה, החדשות הרעות הן שבפרויקט אמיתי הרשימה הזו עשויה להיות גדולה בהרבה. קובץ שעשינו לו add נשמר בתור blob ולכן עלינו רק למצוא איזה מהבלובים האלה מתאים לקובץ שתיקנתי. אחרי חיפוש אני מגלה ש:
$ git cat-file blob dd0221c310613ea89104ce0fede8f10bf6df626a
**The Clockmaker’s friend**
**The Clockmaker’s bug**

הבלוב dd0221c מכיל בדיוק את הקובץ אחרי ה add ולפני שזרקתי הכל עם merge --abort. כדי לקבל חזרה את הקובץ אני מריץ:
git cat-file blob dd0221c3106 > a.txt

ToCode
1 419
בלי ידיים (שני קסמים) מדי פעם אני רואה אנשים רוכבים על אופניים כשהידיים באוויר ולפני כמה חודשים חשבתי שיהיה מעניין לנסות. הרמתי את הידיים מהכידון מרחק 2-3 ס"מ ומיד האופניים סטו הצידה והחזרתי את הידיים לכידון בבהלה. כך זה נמשך כמה שבועות כשכל פעם אני מרים את הידיים מהכידון רק לכמה ס"מ, האופניים ממשיכים לנסוע כמה שניות ואז סוטים הצידה ואני חוזר להחזיק בבהלה. ואז קרה הקסם הראשון. אני זוכר את הרגע הזה היטב, הידיים שלי היו במרחק 2-3 ס"מ מהכידון, האופניים נטו הצידה אבל פתאום הבנתי שבעצם אין לאן למהר. שבעצם הידיים די קרובות לכידון ואני יכול להחזיר אותן קצת יותר לאט. ואנחנו לא מדברים פה על קבועי זמן עצומים, בסך הכל חצי שניה או שניה יותר לאט אבל באותו רגע אותה שניה הרגישה כמו נצח. בפעמים הבאות בתוך מרחב הזמן החדש שקיבלתי שמתי לב לדברים קטנים שקורים, לכל שריר שזז ומשפיע על שיווי המשקל. בהמשך למדתי שאפשר להגדיל את המרחק בין הידיים לכידון, כי ממילא יש לי המון זמן להחזיר אותן כשהאופניים יסטו הצידה וכך קיבלתי יותר זמן לרכב עם הידיים באוויר, כלומר יותר זמן אימון ויותר זמן למידה. בחודשים הבאים ההתקדמות היתה די עקבית וגם די מהנה. המשכתי להשתמש במיומנות החדשה שלמדתי, כל כמה זמן הצלחתי לרכב יותר זמן בלי להחזיק בכידון. שמתי לב לתנועות קטנות שמקלקלות את שיווי המשקל ותנועות קטנות אחרות שמחזירות אותו ובסך הכל כל יום המצב היה קצת יותר טוב. ואז קרה הקסם השני. גם אותו אני זוכר טוב, זה היה תוך כדי נסיעה וחשבתי מעניין אם אפשר לנצל את הסטיה הצידה הזאת גם לדברים טובים, למשל בשביל להסתובב. זה לא הצליח מיד אבל הצליח מספיק בשביל להעביר את המסר. בשבועות שאחרי המשכתי לחפש את התנועה המדויקת ובסוף זה גם הצליח. בלי לשים לב הפכתי לאחד מאותם קוסמים שראיתי בכביש כמה חודשים קודם, ופתאום אני לא מבין מה היה כל כך מסובך. זה נראה כמו משהו שידעתי לעשות תמיד. כמה לקחים לדברים אחרים שאנחנו לומדים שכדאי להשאיר בראש: 1. ההתקדמות היא לא לינארית. בהתחלה לוקח המון זמן להגיע להישגים קטנים ולא מורגשים. 2. יש קפיצות, אבל הן לא לפי הזמנה. 3. תשומת לב היא המפתח להתקדמות, אבל בתחילת הדרך קשה למצוא זמן לשים לב וקשה לשים לב לדברים הנכונים. 4. יש רמת מיומנות שממנה הלימוד מתחיל להיות כיף ואז עושים יותר ממנו. את הדרך הכי ארוכה אנחנו עושים בשלב הזה והרבה אנשים מרגישים שהם "תקועים" כי אנחנו יכולים לבלות שעות בתרגול ולא להרגיש יותר את ההתקדמות. בשלב הזה באמת יש הבדל גדול בין ההתקדמות בפועל לתחושת ההתקדמות. 5. כשאנחנו מלמדים אחרים קל להסתכל על מיומנות שיש לנו ולחשוב "זה ממש קל אז הם בטח יקלטו מהר". יותר מועיל להיזכר כמה זמן לקח לנו להשתלט על מיומנות זו. רוב הסיכויים שזה גם הזמן שאחרים יצטרכו כדי ללמוד את זה.

ToCode
1 419
לקחים שלמדתי על עלויות 1. לתקן קוד יותר זול מלשכתב מערכת. 2. יותר זול לבחור שפת תכנות שאני מכיר מאשר שפת תכנות שאני רוצה ללמוד. 3. יותר זול לבחור שפת תכנות שהרבה אנשים מכירים משפה שרק אני מכיר. 4. מונוליט יותר זול ממיקרו סרביסס. 5. שרת יותר זול מ Serverless. 6. מוצר מדף בתשלום הרבה פעמים יותר זול מפתרון חינמי. 7. המחיר לשעה של פרילאנסר לא אומר הרבה על איכות הקוד. 8. פיתוח פתרון לבעיה שאני מכיר יותר זול מפיתוח פתרון גנרי. 9. לעבוד נכון יותר זול מלעבוד מהר. ומה שלכם?

ToCode
1 419
אתה דווקא לא נראה כמו אחד שמשתמש ב AI למרות שכולם נעזרים ב AI, מפתחים שונים נעזרים בו בדרכים שונות ובאופן כללי לשימוש ב AI כדי לכתוב קוד יש עדיין סטיגמה בעייתית - הרבה מפתחים וותיקים ובמיוחד כאלה שמשתמשים ב AI ברמה בסיסית יגידו לכם שמפתחים שמשתמשים ב AI לא באמת מבינים את הקוד, לא מכירים כל שורה בו. שהקלדה היא ממילא לא צוואר הבקבוק בקידוד ולכן אם היית יודע איך לכתוב את זה לא היית צריך AI. זה יעבור. פעם אמרו דברים דומים על קומפיילר, ואז על שפות עילית, ועל השלמה אוטומטית ואפילו על שימוש בצבעים בעורך הקוד. יש גם קומיקס מפורסם ל xkcd: !Real Programmers השאלה היחידה שחשובה לגבי שימוש ב AI היא האם הבן אדם משתמש ב AI כדי לחסוך זמן או לחסוך הבנה. כלי שחוסך זמן עוזר להתקדם יותר מהר, לא מייצר תלות ומאפשר קוד נקי ונכון יותר. כלי שחוסך הבנה הוא כמו גלגלי עזר באופניים, אולי לא נופלים אבל גם לא ממש מתקדמים. להשתמש ב AI כדי לחסוך זמן אומר שה AI יכתוב לי סקריפט שמשנה מרכאות כפולות לגרש בודד בקבצי YAML, מריץ אותו על ערימה של קבצים ואז מתקן את הסקריפט כי הוא פספס כמה מקרי קצה וממשיך באיטרציה עד שכל הקבצים נכונים. כן יכולתי לכתוב את הסקריפט הזה לבד אבל יש לי דברים יותר טובים לעשות בשעה הזאת. או למפות בכל הקודבייס איזה חלקים מפעילים פונקציה מסוימת ובאיזה תנאים היא תופעל (כי יש בדרך המון if-ים). זה גם יכול להיות לקבל הסבר על פונקציה מסוימת או צורת כתיבה מסוימת במקום ללכת לחפש בגוגל ולראות מהר חלופות שיתאימו לסטנדרטים של הקוד. כמובן לקבל תקציר של שינויים ב diff מסוים ואפילו לתרגם מ Options API ל Composition API ב vue. ולהשתמש ב AI כדי לחסוך הבנה? נו, זה ה"בנה לי משחק סנייק", ה"כתוב בדיקות אוטומטיות לפיצ'ר הזה" או "מזג את הקונפליקט בקובץ". לא מעניין אותי איך זה עובד העיקר שיעבוד. לאורך זמן מפתחים שינסו להשתמש כך ב AI יגלו שהם בונים מערכות מסורבלות, לא עובדות שלא הם ולא ה AI מצליחים לתחזק.

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

ToCode
1 419
ואם זה נכשל? האינטרנט הפסיק לעבוד השבוע. ביום שלישי הלכתי לאסוף משלוח והבחור בסופר התלונן שאי אפשר ללחוץ על הלינק של האישור משלוחים כי יש תקלה באינטרנט העולמי. הוא צדק. חברה בשם Cloudflare נתקלה בקשיים טכניים בגלל קוד ראסט שנראה בערך כך:
let (feature_values, _) = features
    .append_with_names(&self.config.feature_names)
    .unwrap();
הפונקציה unwrap ב Rust מנסה להוציא את הנתונים מאוביקט ה Result שהגיע ממקום אחר, אבל אוביקט Result יכול להחזיק הצלחה או כשלון. פקודת unwrap לא מוכנה לקבל כשלון ותרסק את התוכנית אם אוביקט ה Result מייצג משהו שלא הצליח. התוצאה? התוכנית מתרסקת בלי הסבר, המהנדסים של קלאודפלייר לא מוצאים את הקלט הלא תקין שעבר לפונקציה ובטוחים שיש מתקפת סייבר ואני מסתבך עם איסוף משלוח (וגם מציג פחות דוגמאות בוובינר שהעברתי באותו יום, כי חצי מהאתרים לא עבדו). במקום אחר עזרתי ללקוח לתקן נתונים "נעלמים". חיטוט בקוד הביא אותנו לקטע בסגנון הזה:
if save_data(items):
    report_success()
ומה קורה אם הנתונים לא נשמרו בהצלחה? "זה לא קורה אף פעם...". מתכנתים וגם AI לא אוהבים לחשוב מה קורה כשדברים נכשלים ואיך לגרום למערכות להכשל בצורה חכמה, וזה חבל. מערכות טובות יודעות גם להישבר יפה. נ.ב. זה הסיפור המלא של קלאודפלייר לסקרנים. הוא מרתק: https://blog.cloudflare.com/18-november-2025-outage/

ToCode
1 419
בואו נדבר עוד פעם על הודעות קומיט אני יודע הייתם רוצים לכתוב הודעות קומיט מושקעות, שוכנעתם שזה רעיון טוב אבל פשוט לא יוצא. זה לא אתה זה אני כמו שאומרים. אלה התירוצים שאני שומע הכי הרבה מחברים וחברים לעבודה: 1. ממילא אף אחד לא יקרא את זה. 2. זה רק לבדיקה כדי שאוכל לדחוף את השינוי. 3. תסתכל בשינויים בקוד למה צריך הסבר גם בהודעה. והתשובות בקצרה: 1. אני אקרא. וגם את. וגם אתה. כשתהיה בעיה בקוד הזה, כשמשהו לא יהיה הגיוני, כשעוד שנה נרצה לחזור אחורה ולהבין מאיפה זה הגיע, מי משתמש בזה או למה היה צריך. מה שיש לך בראש עכשיו שווה זהב חבל לאבד את זה. 2. מכירים את rebase? יופי אז בשביל הבדיקות שימו איזה קומיטים שאתם רוצים אין שום בעיה, אבל אחרי שדברים עובדים ואתם באים למזג את הפיצ'ר תקחו כמה דקות תעשו ריבייס תעברו על כל השינויים ותראו שכל שינוי נמצא שם מסיבה טובה. אם עבדתם על הפיצ'ר יומיים אני בטוח שתוכלו למצוא עוד 5 דקות כדי לעשות את זה מושלם. 3. שינוי בקוד יגיד לי שבמסך X נוספה תיבה Y. הודעת קומיט תספר לי איזה אלטרנטיבות שקלתם למימוש, למה החלטתם על קומפוננטה כזאת ולא אחרת, איזה מקרי קצה היו לכם בראש, מה חשבתם שהולך לקרות למסך הזה בעתיד ואיך תכננתם להתאים את העיצוב והתיבה לאותו עתיד מתוכנן. בקיצור כל המחשבות והמודל המנטאלי של הקוד והמערכת שגרמו לכם לכתוב את הקוד שאני קורא. שימו לב - הודעת קומיט צריכה להתאים לשינויים ומותר (ורצוי) להפריד שינויים בנושאים שונים לקומיטים שונים. "תיקנתי באג" לא עוזר, במיוחד אם הקוד באותו קומיט מכניס גם שני פיצ'רים חדשים.

ToCode
1 419
יותר טוב מ ChatGPT - תרגיל שיפור קוד בפייתון נתון הקוד הבא:
import sys

def print_1(text):
    print(f"*{text}*")

def print_2(text):
    print(text)

def print_3(text):
    print(f"# {text}")

def handle(output_type, text):
    method_name = f"print_{output_type}"
    globals()[method_name](text)

if __name__ == "__main__":
    handle(*sys.argv[1:])
הסבירו מה הבעיות בו מבחינת קריאות ושפרו את הקוד מבלי לשנות את ההתנהגות שלו ואת אופן הפעלתו. צעד 1 - שמות הפונקציות הבעיה הראשונה שקופצת לעין היא השמות של הפונקציות. אנחנו לא אוהבים לקרוא לפונקציות בשם גנרי ואז מספר אלא בשם שמסביר מה הפונקציה עושה, לכן הדבר הראשון שנעשה הוא לתקן את השמות של הפונקציות. הבעיה היא ששינוי שם הפונקציות מכריח אותנו לוותר על ההפעלה ה"גנרית" שלהן ולכן נקבל גירסה יותר ארוכה של הקוד:
import sys

def print_italics(text):
    print(f"*{text}*")

def print_body(text):
    print(text)

def print_header(text):
    print(f"# {text}")

def handle(output_type, text):
    match output_type:
        case "1":
            print_italics(text)

        case "2":
            print_body(text)

        case "3":
            print_header(text)


if __name__ == "__main__":
    handle(*sys.argv[1:])
צעד 2 - מידול המיפוי הסקריפט מקבל מספר וטקסט ומדפיס את הטקסט לפי המספר. זה אולי לא מושלם אבל בתרגיל ביקשתי לא לשנות את החתימה של הקוד. עדיין כשהמיפוי "מוחבא" בתוך הפונקציה handle יהיה לי קשה בעתיד למצוא אותו, במיוחד ככל שהפרויקט יגדל. צעד טוב קדימה יהיה להגדיר את המיפוי בצורה יותר מסודרת, או בקלאס או בפונקציה. אחרי השינוי אני מקבל:
import sys

def print_italics(text):
    print(f"*{text}*")

def print_body(text):
    print(text)

def print_header(text):
    print(f"# {text}")


class OutputTypeMapping:
    mapping = {
            "1": print_italics,
            "2": print_body,
            "3": print_header
            }
    
    @staticmethod
    def get_handler_for_code(code):
        return OutputTypeMapping.mapping[code]

    
def handle(output_type, text):
    OutputTypeMapping.get_handler_for_code(output_type)(text)

if __name__ == "__main__":
    handle(*sys.argv[1:])
סקירה ומחשבות קדימה כשמסתכלים על הגירסה השלישית מיד עולה השאלה "למה לא לשים את הכל בקלאס אחד", כלומר למה לא לכתוב קלאס של MarkdownFormatter שיחזיק גם את המיפוי וגם את פונקציות ההדפסה. התשובה שלי היא קודם כל שזה לא יתרום לקריאות של הקוד, הגירסה האחרונה כבר תיקנה את הבעיה הכי מרכזית בקוד הקודם וכבר די קריאה, ויותר מזה שינוי כזה עלול להקשות אם נרצה בעתיד להוסיף עוד Formatter, אולי הדפסה ל HTML. ההפרדה בין "מיפוי קוד לפעולה" לבין מימוש הפעולות היא הפרדה הגיונית. מחשבה שניה נוגעת ליכולת של כלי AI להתמודד עם תרגילים כאלה. ג'יפיטי 5.1 השיב לתרגיל את הגירסה הבאה:
import sys

def print_1(text):
    print(f"*{text}*")

def print_2(text):
    print(text)

def print_3(text):
    print(f"# {text}")

PRINT_HANDLERS = {
    "1": print_1,
    "2": print_2,
    "3": print_3,
}

def handle(output_type, text):
    try:
        handler = PRINT_HANDLERS[output_type]
    except KeyError:
        raise KeyError(f"Unknown output type: {output_type}")  # שגיאה ברורה יותר
    handler(text)

if __name__ == "__main__":
    handle(*sys.argv[1:])
וגם Gemini וגם קלוד שמרו את השמות של הפונקציות עם המספרים. ככל שכלים אלה הולכים ללוות אותנו בשנים הקרובות חשוב לשים לב להנחות אותם ולקבוע בעצמנו את ה Coding Standards ומבנה הקוד כי בסוף אנחנו אלה שנצטרך לתחזק את המערכות שהם בונים.

ToCode - Telegram 频道 @tocodeil 的统计与分析