דף הבית > תכנות > שוב על enum

שוב על enum

כבר יצא לי לכתוב בעבר על שימוש לא נכון ב enum.

ויצא לי להתייחס לנקודה הזו בעבודה עם קולגות, די הרבה פעמים.

מצד אחד, מאוד נוח (ומפתה) דווקא כן לעטוף ערימה של קבועים ב enum, כי זה מעלה את הפרודוקטיביות כאשר משתמשים ב IDE כמו Visual Studio או IntelliJ Idea. שם כל מנגנוני ההשלמה האוטומטית פועלים יפה.

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

הבעיה

במקום לנפנף בידיים, אני אסביר ע"י קוד (ב Java, שבה אני מתכנת כיום).

נניח שיש לנו את ה enum הבא:

ויש לנו בקוד תנאי כזה:

עכשיו, בגלל שזה enum, אז נכון לזמן כתיבת הקוד, אם התנאי מתקיים, כלומר x הוא אכן Green, אז הבלוק של ה else לא יתבצע. כלומר, וזה ניואנס קריטי: הקוד של ה else יתבצע רק אם x הוא White או אם x הוא Blue.

ובכן, אם נוסיף עכשיו ערך חדש ל enum שלנו, נניח Brown:

התוכנית שלנו רצה, ועכשיו ערכו של x הוא Brown.

האם הקוד של ה else יתבצע?

ברור שיתבצע, הרי x איננו Green.

אבל ראינו קודם, לפני השינוי של ה enum, שהקוד של ה else אמור להתבצע רק במקרה ש x הוא White או Blue.

בקיצור, הוספת ערך ל enum שלנו שברה את הלוגיקה של הקוד.

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

זה כל הסיפור.

כאמור, enum אמור להתייחס לקבוצה סגורה של ערכים, שמאוד לא סביר שתשתנה. כמו שמתכנת אחד (הי אסף!) אמר לי: אי אפשר להוסיף עוד יום בשבוע.

שינוי של enum עלול לשבור נכונות של קוד.

פשרות

החיים מלאי פשרות. וזה נכון גם בהנדסת תוכנה.

יכול לבוא מישהו ולומר: אוקיי, ברור לי מהו הסיכון. ולמרות זאת, הפרודוקטיביות חשובה לי. מה עושים?

פשרה ראשונה – ליצור קבועים שלא דרך enum

ב Java זה די פשוט: יוצרים public static final ובזה תמה יצירת קבוע סטטי.

מה הרווחנו:

  • קוד נכון יותר, שפתוח לשינויים
  • השלמה אוטומטית

מה איבדנו:

  • את היכולת לעבור על כל הקבועים האלה (אם ממש רוצים – פתיר ע"י מעטפת ב class יחיד ומעבר ע"י reflection).

פשרה שניה – ליצור enum עם חשיפה מינימלית

אפשר ליצור enum שיהיה private ברמת ה class (או ה package), ואז הסיכון של שבירת הקוד מצטמצם לטווח ההכרה של ה enum שיצרנו.

מה הרווחנו:

  • את כל היתרונות של enum: השלמה אוטומטית, מעבר על הערכים

מה הפסדנו:

  • פוטנציאל לשבירת קוד, אבל בסיכון מצומצם

סיכום

עבודה נכונה עם enum – מתחילה עם הבנה טובה ויסודית של הקונספט עצמו.

במיוחד ב Java, מפתה לעבוד עם enum כמעטפת של נתונים (שיכולים להשתנות עם הזמן), משיקולים של פרודוקטיביות.

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

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

קידוד נעים!

קטגוריות:תכנות תגיות:
  1. שני
    22 אוקטובר, 2016 מתוך 19:40 | #1

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

    בהקשר של הקוד הזה:
    if (x == MyColors.Green) {
    doSomething();
    } else {
    doSomethingElse();
    }

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

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

    אני מניחה שבדוגמא שנתת הטיפוס של משתנים הוא מחרוזת – יש כאן פחות הגנה וקשה יותר למצוא את כל המשתנים שהם מהטיפוס color מכיוון שלא הוגדר טיפוס.

    בקשר לפשרה השניה שהצעת היא רלוונטית לדעתי לכל טיפוס שאני כותבים אבל מנוגד לעקרון ה reuse .

  2. 23 אוקטובר, 2016 מתוך 00:35 | #2

    הרעיון הוא כזה: אם הולכים על enum, אז חייבים לוודא שזו קבוצה סגורה. כמו שציינתי: אין עוד יום בשבוע.
    מרגע שהלכת על פתרון של תבנית switch עם exception ב default (שמעיד על שינוי ה enum), או כל פתרון שמתבסס על משמעת עצמית – את מוותרת מראש על זה שה enum סגור לשינויים, ובכך חוטאת למטרה המקורית של enum.
    כדי להפוך את התוכנה שלנו לכזו שניתנת להרחבה, אנחנו חוזרים ל O/CP, ואת זה כתבתי בפוסט הקודם בנושא.
    השימוש בקבועים הוא סוג של פתרון ביניים: גם O/CP וגם סוג של השלמה אוטומטית.
    ונכון לציין, שמרגע שעובדים עם O/CP, אז כנראה שאין משמעות ל else. הרעיון ב else היה להמחיש את ה"סגירות" של ה enum. לדעתי אין לו משהו מקביל בפולימורפיזם.

  3. שני
    24 אוקטובר, 2016 מתוך 15:28 | #3

    תודה על התגובה, עזר לחבר בין שימוש בשפה לבין לבין העקרונות. 🙂
    קראתי את הפוסט הקודם וגם מכירה ממקודם.
    "השימוש בקבועים הוא סוג של פתרון ביניים: גם O/CP וגם סוג של השלמה אוטומטית.
    ונכון לציין, שמרגע שעובדים עם O/CP, אז כנראה שאין משמעות ל else. הרעיון ב else היה להמחיש את ה"סגירות" של ה enum. לדעתי אין לו משהו מקביל בפולימורפיזם"
    else – התכוונת ל default ?
    האם יש לך דוגמא אחרת בה נכון יותר להשתמש בקבועים ולא ב enum ? פשוט בדוגמא שנתת והבעיות שהצגת שימוש בקבועים לא נראה שפותר / מונע את הבעיות.

  4. 24 אוקטובר, 2016 מתוך 23:08 | #4

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

    כתבתי gist קטן שממחיש את הרעיון של פולימורפיזם. אין, ולא אמור להיות בו if-else או switch-case-default.

  5. סתיו
    22 פברואר, 2017 מתוך 14:13 | #5

    בסך הכל מסכים עם הקונספט שאתה מתאר, פרט לכך שהלוגיקה לא נשברה, אתה הנחת שהקוד ״אמור להתבצע רק במקרה ש x הוא White או Blue״ בזמן שהקוד בפירוש אמור לרוץ רק כשהוא לא Green, וזה עדיין נכון.

  1. אין הפניות עדיין.

Quantcast