כאשר פונקציה נכשלת בלי להודיע לך

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

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

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

עד כאן, לא מעניין במיוחד. בתוכנות יש באגים. הנקודה שבה זה מתחיל להיות מעניין היא כשמנסים להבין למה על חלונות התוכנה עובדת. מסתבר שעל חלונות, אם שולחים את הצירוף “חלון בן” ו-TOPMOST, הפונקציה לא עושה כלום. שוב, לא שום דבר יוצא דופן. מה שבאמת יוצא דופן זה זה – הפונקציה לא עושה כלום, אבל מחזירה ערך שאומר “הצלחה”. במילים אחרות, הפונקציה מדווחת הצלחה בעודה נכשלת.

ומכאן המוטו של Wine: ‏Imitating Windows, bug for bug

שחר

מאת

שחר שמש

מייסד–שותף וחבר ועד בתנועה לזכויות דיגיטליות מייסד שותף בעמותת „המקור”. פעיל קוד פתוח. מפתח שפת התכנות Practical

16 תגובות בנושא “כאשר פונקציה נכשלת בלי להודיע לך”

  1. איך זה באג? הפונקציה התבקשה לבצע דבר מה וביצעה אותו לפי האפיון (שסביר להניח שאומר שהקריאה תעשה דבר מה רק לחלונות). ברור שהיא הצליחה.

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

  2. מקובל להחזיר כישלון עם הסבר: “אובייקט לא קיים”. המקרה בו מקובל להתייחס לתוצאה הסופית בלי קשר למה שהפונקציה עשתה או לא, העיקר שהאובייקט איננו, הוא בניקוי ואיסוף זבל לפני סגירת התוכנה.

  3. איפה להתחיל?

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

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

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

    שחר

  4. זה הכל ענין של הגדרה מהי הפעולה. אם הפעולה היא הצגה פיזית של אותו רכיב GUI לפי הבקשה, אז היא נכשלה, אבל אם הפעולה היא *בקשה לבצע את מירב המאמצים* אז היא הצליחה.

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

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

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

  5. אני חושש שהתבלבלת קשות לגבי איפה פה הבאג. הבאג הוא בחלונות, לא ב-Wine.

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

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

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

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

    כאשר מנסים בפועל להפעיל את הפונקציה כפי שמיקרוסופט מימשה אותה ב-Windows XP, מגלים שהפונקציה מתנהגת שונה מהגדרות הממשק שלה. בפרט, אם מנסים לגרום לחלון בן להיות TOPMOST, לא מתבצעת שום פעולה. החלון לא נהיה TOPMOST, אבל הוא גם לא משנה את גודלו, את מיקומו, והוא גם לא מוסתר אם מבקשים שיהיה. החלון נותר ללא שינוי. במילון שלי, פונקציה שאמורה לשנות ארבעה מאפיינים של חלון ולא משנה אף אחד מהם נקראת “הפונקציה לא עושה כלום”.

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

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

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

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

    שחר

  6. מזכיר לי את ההסבר מדוע הפונקציה free ב-C אינה מחזירה ערך הצלחה או כשלון – מדוע היא void.

    ואם תקבל קוד החזר ש-free נכשל, מה תעשה?

  7. מה זאת אומרת מה תעשה:
    1) תכתוב את זה באיזשהו לוג.
    2) תתן אינדיקציה למשתמש.
    3) תבדוק אם זה קורה מספיק פעמים, ואם כן, תאתחל את התוכנה.

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

  8. 1) “free failed. This should never happen!!!!”

    2)
    free() failed in application myApp.exe [OK] [Cancel]

    3) כן. המשתמש ממש יאהב את זה

  9. לא הבנתי מה אתה טוען.
    אתה טענת כי הסיבה שfree לא מחזירה ערך היא כי גם אם הפעולה נכשלת אין למתכנת מה לעשות (בניגוד לmalloc, שם המתכנת איךשהו יטפל בתקלה).
    אני אמרתי שזה לא נכון, כי תמיד כדאי שתדע על זה, תכתוב בlog או תאתחל את התוכנה (אל תחשוב על תכנת GUI אלא על תכנת שרת שצריכה להיות יציבה, אם קורים דברים מוזרים תאתחל ותדווח. יש תוכנות שמאתחלות את השרת שלך אוטומאטית אן הוא משתמש ביותר מדי זיכרון).
    בנוסף, הבאתי הסבר אלטרנטיבי לפיו מתכנני הAPI סברו שפעולת free לא יכולה להכשל, ולכן היא לא נותנת אינדיקציה כזו.

    אז האם אתה חוזר בך מההסבר “אין לי מה לעשות” ומסביר כמוני “אם הייתי יודע שהיא נכשלת, היה לי הרבה מה לעשות, אבל הפעולה לא יכולה להכשל” או לא.

  10. אני ממש לא מסכים.

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

    שחר

  11. סליחה שלא קראתי את הפירוט הזה כי הוא נראה לי לחלוטיןל לא רלוונטי.

    מי שקובע מה הפונקציה צריכה לעשות אלו המפתחים של MS. בהחלט יתכן שהם שגו בהגדרות שהפונקציה היתה צריכה להתנהל כמו שאתה חושב, אבל כל עוד WINE אמורים להיות נאמנים לצורה בה MS הגדירו את הפונקציה, דעתם האישית של המפתחים על האיכות שלה לא חשובה והן שום סיבה שהם “ישפרו” את הממשקים שלה.

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

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

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

  13. סליחה שלא התעמקתי בתגובה שלך, אלא אני ממציא מה שאני חושב שענית ואז מכסח לזה את הצורה.

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

    שחר

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

    ההערות שכתבתי קודם עדיין רלוונטיות.
    1) אם הפונקציה עלולה להכשל. יש לי רצון לתעד את זה בקובץ לוג אצל המשתמש, בשביל שאם תהיה אצלו בעיה אוכל להציץ בקובץ ולראות מה קרה. לבדוק מה התקלה אצל משתמש זה קשה, ואם אני אדע את זה זה יכול לחסוך לי הרבה זמן.
    2) אם אנחנו מדברים על תוכנת שרת, או תוכנה חיונית למערכת ההפעלה, שאמורה לרוץ הרבה זמן, ייתכן שהיא תרצה לאתחל את עצמה או את המחשב (אם יש בעיה במבנה הנתונים, ייתכן שאיתחול של התוכנה לא יעזור). זו התנהגות נורמלית, ולמעשה שילמו לי פעם בשביל שאוסיף התנהגות כזו לשרת של איזה אתר ישראלי (קרא על monit).
    3) לדעתי גם הודעה למשתמש בסגנון “משהו מוזר קרה, ייתכן ותראה עליה בשימוש בזיכרון” היא יותר טובה מאשר לא להודיע כלום ולקבל טלפונים זועמים כשזה כן קורה, אבל על זה אפשר להתווכח.

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

כתיבת תגובה

האימייל לא יוצג באתר. שדות החובה מסומנים *

Bear