קוד טוב יותר #1 - סדר בעיניים

[3 באוגוסט 2010] [8 תגובות]

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

הנה טיפ קטן שעוזר לשמור על הסדר. למדתי אותו מדוגמאות של Microsoft לפיתוח דרייברים ל-Windows, אבל הוא מתאים כמעט לכל שפות התכנות ואני נעזר בו במקרים רבים.

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

כשמתחילים לכתוב פתרון כזה, בדרך כלל מתקבלת תוצאה שדומה לקוד הבא:

if (someObject != null)
{
        if (checkSomething() >= 5)
        {
                if (checkSomethingElse() >= 5)
                {
                        if (initFunction() == RESULT_OK)
                        {
                                if (doSomething() == RESULT_OK)
                                {
                                        printf("Success!");
                                }
                                else
                                {
                                        printf("failed to do");
                                }
                        }
                        else
                        {
                                printf("failed to init");
                        }
                }
                else
                {
                        printf("also less than 5");
                }
        else
        {
                printf("less than 5");
        }
}
else
{
        printf("null pointer");
}

כמו שאתם רואים הקוד לא נראה ממש טוב. אם נמשיך להוסיף בדיקות, זה עלול להיגמר בשורות מאוד מאוד ארוכות ולא יפות. כשקשה לקרוא את הקוד, קשה להבין אותו.

ניתן להפוך את הקוד הזה למסודר יותר על ידי שימוש בלולאת While שתמיד מבצעת איטרציה אחת של לולאה. כן, למעשה זוהי אינה ממש לולאה. היתרון הוא שניתן לבצע break מתוך הלולאה ולקפוץ תמיד לסופה כאשר משהו משתבש.

הנה קוד שנראה מעט טוב יותר:

do
{
        if (someObject == null)
        {
                printf("null pointer");
                break;
        }

        if (checkSomething() < 5)
        {
                printf("less than 5");
                break;
        }

        if (checkSomethingElse() < 5)
        {
                printf("also less than 5");
                break;
        }

        if (initFunction() != RESULT_OK)
        {
                printf("failed to init");
                break;
        }

        if (doSomething() != RESULT_OK)
        {
                printf("failed to do");
                break;
        }

       printf("Success!");

} while (0)

בצורה זאת ניתן להוסיף המון פעולות ברצף והקוד ממשיך להיות מסודר.

פתרון אחר (ודומה) הוא לכתוב פונקציה שמבצעת את הבדיקות האלו, כאשר בתוך הפונקציה אין צורך בלולאה, וניתן לסיים על ידי return במקום על ידי break.

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

אני אוסף כל מיני טיפים קטנים. אני מקווה לפרסם אותם כאן בקרוב.

 

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

8 תגובות

  1. דוד | 8/3/2010 3:12:09 PM
    מה עם break אחרי 
    1.        printf("Success!");  
    2. ?
    3. יש מצב שיש כאן לולאה אין סופית??
    4. אני חושב שאני לא הייתי שמח לקרוא לולאה רק בשביל לחסוך את כל הelse. אם כבר קריאות - הייתי הולך על הפונקציה, ודואג לביצועים רק אם הפרופיילר אומר שכדאי.
    5. ממליץ להכיר את refactoring של fowler: http://www.refactoring.com/catalog/replaceNestedConditionalWithGuardClauses.html
  2. טל | 8/3/2010 10:18:40 PM

    אין צורך ב-break בסיום מפני שהתנאי על הלולאה הוא 0. המשמעות היא כמו false, כלומר, תמיד לסיים את הלולאה.

    תודה על הלינק :) אני הולך לקרוא

  3. דוד | 8/3/2010 11:06:20 PM

    טעות שלי.

    נראה מאוד לי טבעי השימוש בלולאה כאן... 

  4. אוהב ספגטי | 8/9/2010 7:49:51 AM
    יוחזר הGOTO!
  5. אלזוב | 2/12/2011 6:47:38 PM

    אבל בדיוק בשביל זה אפשר לשרשר תנאים עם else if..
    (למרות שבשפות המבוססות על C לא מדובר בפיצ'ר של השפה, אלא הביטוי מתפרש כ-if מקונן בתוך else)

     הקוד ה-"טוב" וה-"קריא" היה אמור להיות:

    if (condition1) { statements1; }‎

    else if (condition2) { statements2; }‎

    else if (condition3) { statements3; }‎

    else if (condition4) { statements4; }‎

    ...

    else { deafault statements; }‎ 

  6. טל | 2/13/2011 11:17:08 AM

    תוכל לכתוב קוד שמבצע בדיוק את אותה הפעולה כמו בפוסט בעזרת מבנה ה-else if שהצעת? 

    שים לב שבמבנה ה-else if שהצעת, אם תנאי 1 מצליח, כל הפעולה נגמרת.

    בפוסט מתואר מצב שרוצים לבצע מספר פעולות אך להפסיק לאחר שאחת נכשלה.

    תוכל להגיד, שניתן להגדיר כל else if בשלילה. כלומר ה-if מבצע את הפעולה ואם היא נכשלה, אז התנאי מתקיים ומפסיקים לרדת במורד ה-else if האחרים.

    זה ממצב יהיה מסורבל וחסרון נוסף הוא שחייבים לבצע את הפעולה בתוך תנאי ה-if.

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

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

  7. אלזוב | 2/13/2011 7:12:15 PM

    אולי זו השעה המאוחרת, אבל לא הבנתי איך זה שונה ממה שהצעת.
    גם אצלך אם התנאי הראשון של someObject==null מתקיים, אז כל הפעולה נגמרת.

     איך הקוד שלך פועל אחרת מהקוד הזה?

     if (someObject == null) { printf("null pointer"); }‎

    else if (checkSomething() < 5) { printf("less than 5"); }‎

    else if (checkSomethingElse() < 5) { printf("also less than 5"); }‎

    else if (initFunction() != RESULT_OK) { printf("failed to init"); }‎

    else if (doSomething() != RESULT_OK) { printf("failed to do"); }‎

    else { printf("Success!"); }‎


  8. שמוליק | 11/10/2013 12:58:00 PM
    זה צריך להיות חלק מהשפה, לא האק. הא, בעצם יש דבר כה קוראים לו goto

התגובות נעולות