האם goto באמת שטני?
מאת TAsn בתאריך 26/01/09, תחת כללי
מבוא
יש כל מיני "שיטות" תכנות, יש אנשים שמתכנתים "פרוצדוראלי", יש אנשים שמתכנתים "מונחה עצמים", יש אנשים שמעדיפים אבסטרקטיות ויש אנשים שלא. קיימות הרבה טענות בעד ונגד כל אחת מהשיטות, ישנה הסכמה שחלק מהשיטות נכונות יותר למקרים מסויימים, וחלק לאחרים, בקיצור, כולם מסכימים שאין שיטה אחת מושלמת, או לחלופין, נוראית. אולם, ישנה הסכמה ש goto הוא שליחו של השטן לעולם התיכנות.
לאלה מכם שלא יודעים goto זו קפיצה בלתי מותנית ב c (ודומותיה).
דוגמא לשימוש בקוד:
1. goto exit; 2. printf("hello world!\n"); 3. exit: return 0;
בקוד הזה, שורה 2 לעולם לא תקרא, שורה 1 תגרום לקפיצה בלתי מותנית (קרי: תמיד) לשורה 3, אשר תסיים את הרצת הפונקציה.
אז למה בעצם goto נחשב רע?
לפי דעתי ישנן כמה סיבות אפשריות:
1. goto נחשב רע עקב רצון שאנשים ישתמשו באלטרנטיבות היותר "high level" שלו, כמו for, while, if, try, etc… ובשביל שאנשים באמת ישתמשו בהם, התחילו להעליל על goto.
הנה לדוגמא קוד ב goto (סטייל אסמבלי) וקוד עם for:
for (i=0; i<5 ; i++) print("hi");
לעומת הקוד עם goto:
i=0; start: if (i<5) print ("hi"); i++; goto start; exit:
2. יותר מידי אנשים שהיו רגילים לאסמבלי ששם אפשר לעשות שטויות עם goto שאי אפשר לעשות ב c, לדוגמא "קפיצות רחוקות" החליטו שלא רוצים בלאגן בקוד שלהם, ולכן צריך גישה חדשה, נטולת goto.
הנה קוד שלא אפשרי ב c אבל זו הגישה המדוברת והממש מעצבנת לעקיבה:
int foo (int i) { goto a; return 0; } int bar (int j) { a: print("hi"); return 0; }
3. בעצם אנשים עשו בלאגן עם goto בעבר הרחוק ואז אנשים התרגלו לראות goto ולברוח בזוועה.
לדוגמא:
while (i<6) { start: for (j=0; j<5; j++) { if (j==3) goto start; } i++; }
למה כן
כל אחת מהסיבות האלה הגיונית ואפשרית אבל האם הן מספקות? הרי אפשר להתעלל בעוד הרבה פקודות ב C אם לא עושים דברים נכון. זה כמו לאסור להשתמש במצביעים ל void הרי עם שימוש במצביעים כאלה אין type checking בחלק מהמקומות וזה יכול להוביל לשגיאות. זה פשוט לא הגיוני!
לעומת הטענות החלשות נגד goto יש טענות חזקות בעד:
1. אנשים משתמשים ב goto (בלי לדעת) כל הזמן.
2. goto עוזר מאוד בכל מיני מקרים.
סינטקס קיים שפועל כמו goto
1. break ו continue זה goto שעטפו אותו מעט! הרי בקלות אפשר לממש את שניהם בעזרת goto:
while (true) { if (i>1) continue; else break; }
ועם goto:
while (true) { next: if (i>1) goto next; else goto end; } end:
והמימוש עם לולאות for לא שונה בהרבה!
מה בנוגע ל try and catch? (כן, אני יודע שזה c++)
try { if (error) { throw "error!"; } } catch (char * str) { printf("%s\n", str); }
לעומת:
if (error) { str = "error!"; } else { goto cont; } /* catch */ error: printf("%s\n", str); cont:
אז נכון, להשתמש בסינטקס הקיים יותר נקי, ובטח מרגיש יותר נכון, אבל הוא התחיל כ goto סתם עטפו אותו יפה, ואם הוא בעצם מתנהג כמו goto, אז הרי בטח יש לו את אותן תכונות שטניות…
הסינטקסטים שנתתי מגבילים את המתכנת לא לעשות שטויות מוגזמות, אבל ע"י כתיבה נכונה גם עם goto אפשר לעשות קוד נקי ומובן.
שימושים נכונים:
אני לדוגמא משתמש ב goto רק בשימוש אחד (אני חייב להודות שקיבלתי השראה מהקוד של הקרנל…) ניקוי פונקצייה אחרי שגיאה:
לדוגמא ללא שימוש ב goto:
int foo (int i) { char *a; char *b; char *c; a = malloc(5); if (a == null) return -1; b = malloc(6); if (b == null) { free(a); return -1; } c = malloc(7); if (c == null) { free(a); free(b); return -1; } return 0; }
לעומת ניקיון בעזרת goto:
int foo (int i) { int ret=0; char *a; char *b; char *c; a = malloc(5); if (a == null) goto errora; b = malloc(6); if (b == null) goto errorb; c = malloc(7); if (c == null) goto errorc; exit: return ret; /* error handling section */ errorc: free(b); errorb: free(a); errora: ret = -1; goto exit; }
הניקיון בעזרת goto יותר נקי, פה "קשה" יותר לראות את זה, אבל כאשר מתעסקים עם הרבה זכרון דינמי או ניקיון שצריך לעשות לפני עזיבת הפונקצייה, להעתיק את הכל מחדש זה מיותר ויכול לגרום לשגיאות, ככה, אפשר להכניס לכל מקום בקוד שורות נוספות ולדאוג לניקיון ב"חלק האיסוף".
עוד דוגמא:
int foo (int i) { int stop = 0; while (true) { while (true) { if (i==3) { stop = 1; break; } } if (stop) break; } /*more code */ }
לעומת:
int foo (int i) { while (true) { while (true) { if (i==3) goto end; } } end: /*more code */ }
או עוד דוגמא:
if (a) { if (b) { if (c) { printf("a "); printf("= b = c = true\n"); } } else { if (d) { printf("a = !b = d = true\n"); } } }
לעומת:
if (!a) goto end; if (b) { if (! c) goto endc; printf("a "); printf("= b = c = true\n"); endc: } else { if (! d) goto endd; printf("a = !b = d = true\n"); endd: } end:
שהרבה יותר ברור (ויותר חשוב, מונע הזחות מיותרות!).
ממה בכל זאת צריך להמנע
כמו שאמרתי, יש לgoto יתרונות כל עוד נמנעים מכמה דברים חשובים:
1. בלי קפיצות רחוקות (ב c במילא אי אפשר, אבל למקרה שאתם לא מתכנתים ב c). לקפוץ מפונקציה לפונקציה זה פסול, לא נכון, וימלא את המחסנית בזבל (חוץ מזה שזה יכול לגרום לשגיאות).
2. בלי קפיצות "אחורה".
לא מומלץ לכתוב קוד שבו הקפיצה תוביל "למעלה" במעלי הקוד, כלומר לקוד שהורץ לפני ה goto, לדוגמא, לא לעשות דבר כזה:
start: /*code*/ goto start;
3. יש לבחור תויות בעלות משמעות. כמו שבוחרים שמות משתנים ופונקציות בעלות משמעות, כך גם צריך להתייחס לתוויות, הן צריכות להסביר את תפקידן בקצרה ובמקרה שצריך, אפשר להוסיף תיעוד קל.
צריך לשמור על קונבצניות בבחירת השם, בדיוק כמו שעושים עם משתנים ופונקציות.
4. יש לכם עוד רעיונות? תכתוב בתגובות…
אני מקווה שעד עכשיו אתם מסכימים של goto יש מקום בעולם ואסור לפסול אותו על הסף, כי גם לו יש שימוש מעניינים ונכונים.
26/01/09 בשעה 6:18
אני תמיד משתמש בgoto במקרה של נקיון דברים רבים שצריך לשחרר לפי הסדר..
זיכרון, Handles, Sockets, וכו'..
בלי זה זה פשוט Copy-Paste של הקוד שמנקה וזה פשוט נורא…
27/01/09 בשעה 9:57
אני צריך לערוך קצת את הפוסט, להסביר מה גרם לי לכתוב אותו (עכשיו שאני פחות גוסס) אבל בעקרון זה ויכוח לא נגמר עם הרבה אנשים שיצא לי לכתוב איתם קוד \ לדבר איתם על קוד שנגעלו מהעובדה שאני משתמש ב goto.
אני משתמש ב goto בדיוק באותם מצבים שציינת, וזו הדרך הנכונה לעשות, copy paste זו פשוט שגיאה נוראית!
28/01/09 בשעה 0:54
http://xkcd.com/292/
28/01/09 בשעה 2:57
חלק מהקודים כאן לא דורשים בכלל את הלולאות בנוסף ל goto ויש עוד כל מיני דברים הזויים כמו..הטעויות הנפוצות כשמשתמשים ב goto
תקרא שוב כשתרגיש יותר טוב 🙂
28/01/09 בשעה 3:36
ברור שחלק מהקודים לא דורשים את הלולאות, הקודים כאן הם מבנה, והייתי צריך לדחוף קצת קוד בתוכו, אז במקום לסבך אתכם עם תוכנית חופרת כתבתי דברים קצרים (שחלק מהזמן זורקים מהלולאה, ואז מבטלים את הצורך שלה אבל זה בכוונה, רק בשביל הדוגמא). לא עשיתי חלק על טעויות נפוצות שמתמשים בגוטו, על מה אתה מדבר?! (אני עדיין חולה ד"א).
28/01/09 בשעה 22:12
האמת שהתעייפתי באמצע הקריאה אבל סחטן על החפירה Tasn 😉
חח
בכל מקרה אני לא מוצא סיבה למה להחשיב את goto לדבר רע
הרי ש-goto הוא מקביל מדוייק ל-jmp.
והוא יכול להיות שימושי להרבה דברים שלא עולים לי לראש
עכשיו חח.
אבל חוץ מזה עזבו אותכם שטויות אני אומר עזבו מחשבים
וחארטות בואו נעשן סמים ונהגר לדרום אמריקה או משהו
29/01/09 בשעה 18:30
אני גם לא מוצא סיבה להחשיב את goto כרע! אבל יש אנשים מוזרים בעולם. אפילו אנזס התווכח איתי (והוא ליט) אז זה כנראה לא משהו נקודתי!
עזובתך דרום אמריקה, בוא לאירופה.
30/01/09 בשעה 19:30
כן בדיוק תעשו דיסאסמבלי או דיבגינג למה שתרצו אני מבטיח
לכם שלא יהיה מקום אחד שלא יהיה בו goto.
ואירופה ? נהה כבר יש כרטיס חח זה לא הזמן להתחרט
30/01/09 בשעה 21:27
טוב, אז אל תתחרט… 🙂
ברור שבאסמבלי יהיה goto… ואכן זה גהנום לדבאג, אבל זה לא דוגמא לשימוש החכם והרגוע שאני מדבר עליו ב C 🙂
31/01/09 בשעה 14:27
דרום אמריקה? פשש…תהנה! אני עוד מעט בא 🙂
05/02/09 בשעה 15:03
תודה על הטיפ מהקרנל בנוגע לניקוי עם goto, אני אשקול להשתמש בזה אממ עד כמה שזה מצחיק בקוד ג'אווה שלי.
בג'אווה כמו בג'אווה זה כמובן לא goto אמיתי אלא קישוט יפה (אך שימושי!) שמצאפשר לצאת מתנאי / לולאה לתווית מוגדרת. עוד בלינק הזה : http://geekycoder.wordpress.com/2008/06/25/tipjava-using-block-label-as-goto/
אחלה בלוג, Fate\TAsn.
הפכתי לקורא קבוע.
07/03/09 בשעה 20:09
יש בזה משהו, אני אישית משתמש בו לפעמים (זה ממכר O: ) ואני לא מסכים עם הגישה של "אסור לקפוץ למעלה".
לדוגמה:
void RndToBoard()
{
int i, j;
RAND_AGAIN:
i = rand()%10;
j = rand()%10;
if (board[i][j] != EMPTY)
goto RAND_AGAIN;
board[i][j] = FULL;
}
מניח שהרעיון מובן, כמובן שאפשר לעשות זאת גם עם do while.
בכל אופן, מתכנתים מנוסים רבים מתנגדים לשימוש ב goto, אני משתד ללכת בעקבותיהם (:
26/04/09 בשעה 19:51
gavra, אחי, הדוגמא שלך זה דוגמא קלאסית לשימוש שטני ואסור ב goto, למה לעזאזל שתרצה לעשות goto שאתה יכול לעשות את זה בלולאה בצורה יפה והגיונית?! (ונקייה), ככה שאתה לא מאבד את ההזחה.
אז הדוגמא שלך היא ממש לא דוגמא נגדית לטענתי.
10/06/09 בשעה 12:14
הקודים שכתבת פה הם מגעילים בלשון המעטה. ישנה סיבה למה "goto statements considered harmful", והדוגמאות שנתת פה הן דוגמאות לא משהו. כשתגיע לרמה שבה אתה יכול לתרום לקרנל תשתמש ב-goto עד אז אני מציע שתקשיב לאנשים חכמים ממך.
12/06/09 בשעה 17:52
working programmer, יש לך דוגמאות לאיך אתה היית עושה את זה? goto is considered harmful רק בגלל שטיפת מח מפגרת שאנשים עשו נגד זה בגלל אנשים שלא יודע לכתוב קוד. הדוגמאות שנתתי הן דוגמאות לקוד גרוע, ואחרי זה קוד עם goto ואני חושב ללא ספק שהקוד עם ה goto נקי יותר ונכון יותר.
כמו שאתה בטח יודע PHP החדש קיבל תמיכה ב goto, למה אתה חושב שעשו את זה? כי goto זה רע?
בנוסף, למה אתה חושב שבקרנל משתמשים ב goto? כי זה רעיון טוב, לא צריך "להגיע לרמה" מה זו בכלל הרמה שאתה מדבר עליה? זה היופי בקוד פתוח, כל אחד יכול לתרום קוד, רק צריך להתאים את עצמך למה שכבר קיים שם.
באופן כללי, אני לא מצליח להבין מה ניסית להעביר בתגובה שלך, לזרוק איזה הערה חסרת ביסוס (כמו שאמרתי, אני מחכה שתציע אלטרנטיבות) ואז לזרוק הערה כמו "שתקשיב לאנשים חכמים ממך."
בקיצור, תקרא עוד בנושא, אולי תלמד, לא אכפת לי שהרבה אנשים נגד GOTO, הרבה אנשים נגד הרבה מאוד דברים טובים, רק בגלל שהם לא "מוכנים לזה".
בנוסף, מה יש לך להגיד על ניקוי שגיאות בפונקציות?
לאחרונה היה ב whatsup דיון ושם העלו כל מיני "פתרונות" אבל לאף אחד לא היה פתרון על איך לנקות פונקציות בצורה יפה בלי goto. או בקיצור, אני רוצה לשמוע מה יש לך לאמר, אבל תגיד אותו, אל תכתוב פה הערות חסרות תוכן 🙂
27/11/09 בשעה 15:56
וואו עבר זמן..
הייתה תקופה שהייתי משתמש בגוטו פשוט סתם ללא צורך.
משום מה היה לי יותר נוח לחשוב "נרנדמל, אם לא טוב נרנדמל שוב" אך כמובן שהרבה יותר פשוט לומר "נרנדמל כל עוד".
בכל אופן אני לא רואה מדוע השימוש שהדגמתי הוא כ"כ שטני, אני יותר רואה אותו כטיפשי ומיותר.
27/11/09 בשעה 17:05
gavra, זה יוצר קוד פחות קריא ופחות קל למעקב, וזה בדיוק העניין.
קוד לא קריא = גהנום לקרוא ולהבין למי שרוצה להצטרף לפרוייקט, אבל בעיקר לנסות למצוא שגיאות בתכנון (באגים).
הקוד צריך לשקף את מה שהוא עושה בדרך הפשוטה והברורה ביותר, כדי להקהל על מי שקורא להבין בדיוק מה קורה שם. למכונה זה במילא לא יצור שום הבדל.
בקיצור, מה שהדגמת שטני כי הוא פחות מובן וקריא מאשר הדוגמה השניה, למרות שהדוגמה שלך יותר פשוטה, אז זה פחות נורא. אבל קפיצות מפה לשם וחזרה זה פשוט גהנום.
17/05/11 בשעה 17:32
הרבה אנשים נגד הרבה מאוד דברים טובים, רק בגלל שהם לא "מוכנים לזה"