DCSIMG
Demystifying 'goto' Evilness - Liran Chen's Blog

Liran Chen's Blog

.Net Internals, Debugging, Multithreading - and More!

Demystifying 'goto' Evilness

הפעם, נתחיל בשאלה. אם הייתם נשאלים מה היא לדעתכם מילת המפתח הכי מסוכנת שקיימת היום בעולם התכנות, איך הייתם משיבים? סביר להניח שכמו הרוב המוחלט של מתכנתים הייתם שולפים במהירות בזק את מילת המפתח האלמוותית "goto".
לאורך השנים, מילת המפתח העתיקה הזאת הצליחה לספוג לא מעט ביקורת על היותה פתח מסוכן שיכול להוביל לקוד ספגטי בלתי קריא ובלתי ניתן לתחזוקה. ובכך, היא הצליחה לעשות את הבלתי יאומן ולהגיע למעמד סטטוס מרשים, ולהשיג קונצנזוס מלא על היותה "רעה" (או: Evil). תחשבו על זה, יש לנו כאן מילת מפתח קטנה, שעם הזמן הפכה לשם נרדף לצרות וכאוס טוטאלי. למעשה, הגענו היום למצב בו לא ניתן בכלל להעלות על הדעת שמתכנת שפוי יעז להכניס איזה goto קטן לתוך הקוד. והנה, אם בכל זאת הבלתי יאומן קרה והוא העז לעשות זאת, הוא צפוי לחטוף מייד שלל הרצאות ודקלומים לא נגמרים מעמיתיו שיאמרו לו ש"אסור להשתמש ב-goto אף פעם! היא גורמת רק לצרות, ופשוט מדובר ב-code smell מובהק".
עם זאת, מילת המפתח השנויה במחלוקת הזאת עדיין מצליחה להחזיק את הראש מעל המים ולמצוא את עצמה גם בשפה מודרנית כגון #C. עצם המצאותה של מילת המפתח הזאת ברשימת מילות המפתח של השפה מהווה מטרה קלה עבור כל מי שבא להדריך מתכנתים אחרים שעושים את צעדיהם הראשונים בעולם התכנות. בדרך כלל נוהגים לקטול אותה לגמרי, ומעדיפים לאפסן אותה בעליית הגג של מילות המפתח שלא נשתמש בהן יותר בחיים.
שלא יהיה כאן טעות, הפוסט הזה לא בא לסתור את הטענות האלה ולהפריך אותן לגמרי (כן, שימוש ב-goto יכול להוביל בקלות לקוד ספגטי מזעזע). אבל הוא כן בא לנסות לשים את הכל בפרופורציה. איכשהו יצא שמתכנתים היום מפחדים פחד מוות ממנה, בלי שהם אפילו ראו אי פעם בחייהם קטע קוד שבאמת עושה בה שימוש. הקטע הבעייתי כאן הוא שמילת המפתח goto היא הרי לא באמת "רעה", אלא דווקא שימושית למדי. באמצעות שימוש מושכל וזהיר ב-goto, אפשר להגיע לקוד קריא וברור הרבה יותר מאשר האלטנטיבות שאינן כוללות שימוש בה. אחת הדוגמאות הפופולריות למקרה כזה היא יציאה מלולאה כאשר אנחנו נמצאים בתוך switch statement (כאשר קריאה ל-break תוציא אותנו רק מה-case הנוכחי, אך לא מהלולאה), או יציאה מהירה ונוחה מסדרה של לולאות מקוננות (דבר שללא שימוש ב-goto יוביל לפיזור מוגזם של משפטי if שנועדו אך ורק לשלוט על ה-control flow של הלולאות המקוננות).

    // which option do you find more readable? a single goto statement,

    // or keeping track of the value of the new 'shouldIGetOut' variable?

    while(otherCondition)

    {

        bool shouldIGetOut;

 

        switch (input)

        {

            case 1:

                DoThat();


                shouldIGetOut = true;

                goto GetOutQuick;

        }

 

        if (shouldIGetOut)

            break;

    }

 

GetOutQuick:

    DoThis();


אחד המקורות שנוטים להפנות אליו כאשר רוצים לתת איזשהו סימוכין מהימן לטענות הללו ולהביע למה באמת השימוש ב-goto יכול להיות רע, הוא המאמר המפורסם Go To Statement Considered Harmful מאת Edsger Dijkstra שפורסם כבר בשנת 1968. הבעיה היחידה היא שרבים מאלו שמפנים למאמר המוכר לא טורחים באמת לקרוא אותו, אלא רק נמשכים לכותרת המבטיחה והמוניטין של המחבר שמקנה בטחון רב באשר לתוכנו של המאמר. הקטע המשעשע הוא שדייקסטרה כלל לא היה זה שהגה את שמו של המאמר האלמוותי. לעומת זאת, במקור ניתן למאמר השם הפעוט יותר "A Case Against the goto Statement". את כל זה דייקסטרה מסביר בעצמו:

"...but what had happened? I had submitted a paper under the title "A case against the goto statement", which, in order to speed up its publication, the editor had changed into a "letter to the Editor", and in the process he had given it a new title of his own invention!"

למי שיצא להתנסות מעט בג'אווה, בוודאי שם לב שהשפה לא תומכת במילת המפתח המדוברת. אולם, כן נוספה התמיכה בפיצ'ר הנקרא Multi-Level Break. שאיך שלא מסתכלים על זה, מדובר ב-goto בעל יכולות מצומצמות. עם זאת, בהתחשב בכך שמרבית השימוש (השפוי) ב-goto מסתכם ביציאה מ-switch'ים ולולאות מקוננות, התמיכה ב-Multi-Level Break יכולה להוות תחליף מתאים ונכון ל-goto המלא שעשוי להביא איתו סכנות מיותרות. Sun מציינת שמחקרים מראים שלאחר ניתוח של 100,000 שורות קוד ב-C אפשר להתרשם כי 90% מהשימושים במשפטי goto נעשים על מנת לצאת מלולאות מקוננות. כך ששימוש ב-Multi-Level Break צריך להוות Tradeoff טוב למדי בין השימושיות של goto לבין המנעות מסכנות מיותרות.

בשורה התחתונה? לכוח רב מצטרפת אחריות רבה. זה משפט שאני מוצא את עצמי משתמש בו לא מעט, אבל רק בגלל שהוא כל כך נכון ומתאר בדיוק מה שקורה כאן. שימוש ב-goto יכול לעשות ניסים ונפלאות למידת הקריאות של הקוד. ועם זאת, בשימוש לא זהיר ומושכל אפשר למצוא את עצמנו מתחזקים קוד ספגטי נוראי ומקללים את היום שבו הכרנו את מילת המפתח הזאת. מהסיבה הזאת, עד היום לא מצאתי לנכון להמליץ לאדם אחר להשתמש ב-goto על מנת להפוך את הקוד שלו לבהיר יותר. זה מסוג הדברים שכל אדם צריך להבין בעצמו את כל המעלות והחסרונות של הפיצ'ר המסויים, ולהחליט מתי כן ומתי לא להשתמש בו. עם זאת, אני כן רואה לנכון לגרום לאנשים להיות מעט יותר פתוחים לקראת רעיונות "מטורפים" כמו שימוש ב-goto בקוד. הרי העולם לא יבוא לקיצו אם נקליד את רצף האותיות g-o-t-o. ובכלל, תמיד כששומעים מישהו מדקלם ש"אף פעם לא צריך לעשות X", תמיד צריך לקבל את דבריו בערבון מוגבל. הרי איננו חיים בעולם שחור-לבן, שבנוי על מערכת חוקים נוקשית. לפעמים אפשר להתקל גם בתחומים אפורים, ובחוקים שאפשר מעט לקופף ולעוות כדי שהתמונה המלאה תראה ישרה ונכונה יותר.
לסיכום, אולי פשוט צריך להקשיב למה שדייקסטרה אומר:

"Please don't fall into the trap of believing that I am terribly dogmatical about [the goto statement]. I have the uncomfortable feeling that others are making a religion out of it, as if the conceptual problems of programming could be solved by a single trick, by a simple form of coding discipline!"

תוכן התגובה

Shlomo כתב/ה:

מסכים לכל מילה

# March 20, 2010 10:30 PM

Jasper כתב/ה:

Actually - As I understand this internal structure of switch/case. All code paths (where to jump on witch 'case' based on 'balanced tree' and inside 'case' block there 'goto' command that jumps to the end of bracket '}'.

This was one of the question of job interview: if you have for example 10,000 'case' codition - what the optimal path that you can build internal structure of switch/case block ?

# March 21, 2010 9:58 AM

איתמר כתב/ה:

תקן אותי אם אני טועה, אבל ברמת קוד המכונה goto מקבילה לקריאה לפונקציה. כלומר - ל-byte code מתווספת פקודת jmp למקום הרלוונטי בקוד.

אם כך, השיקול היחיד אם להשתמש ב-goto הוא מבנה הקוד, מבחינת אסטתיות ויעילות, ותו לא.

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

# August 17, 2010 9:53 PM

Liran Chen כתב/ה:

@איתמר,

מבחינת קוד מכונה, קיים הבדל בין קריאה לפונקציה ופקודת goto עקב השימוש ב-calling convention כאשר מדובר בפונקציה.

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

# August 18, 2010 7:21 AM
שלח תגובה

(שדה חובה)  

(שדה חובה)  

(אופציונלי)

(שדה חובה) 

Please add 8 and 8 and type the answer here:


Enter the numbers above: