כשספריית Delphi מוסיפה תצורת בנייה בלי הפריימוורק החזותי, המחלקות התחליפיות הן המקום שבו חיים הבאגים. לא הפלטפורמה, לא המהדר: התחליפים. ל-PDFlibPas יש שכבת גרפיקה שמספקת מקבילות bitmap, canvas, font, metafile ו-printer עבור בניינים בלי VCL, והעברתה ל-Free Pascal חשפה כל מצב כשל שתחליף יכול להיות לו. הם מתמיינים יפה לפי עלות האבחון, והסדר הפוך למה שהאינטואיציה מרמזת
תחליף שזורק חריגה זול למציאה; החריגה קוראת בשם השיטה. תחליף שמחזיר נתונים ריקים יקר, כי הכשל מופיע כמה שכבות רחוק מהגורם שלו. תחליף שמחזיר הצלחה הוא הגרוע מכולם, כי קוד ההחזרה תקין, קוד השגיאה אפס, לא נזרקת חריגה, והראיה היחידה שמשהו השתבש היא בבייטים שיצאו
צורה שלוש: מחוון תמונה תקין על XObject ריק
הממיר של metafile וקטורי היה גוף פרוצדורה ריק בתצורה שאינה VCL. הכול מעליו המשיך לעבוד. נקודות הכניסה של יבוא EMF ונקודת הכניסה של לכידת canvas רצו עד תום והחזירו מחוון תמונה חוקי, שהקורא הציב אז על עמוד. מה שנחת בקובץ היה form XObject עם אורך תוכן אפס. העמוד הוצג לבן
שום דבר לא דיווח על בעיה, וזה כולל את תוכנית ההדגמה של הספרייה עצמה לתכונה הזאת, שציירה עמוד ריק ולא שמה לב. לא היה ערך החזרה נכשל לבדיקה, כי רצף הקריאות באמת הצליח כולו; הדבר היחיד שהיה שגוי היה הגודל של ה-stream שהופק. אבחון מחלקת הפגמים הזאת פירושו לשאול שאלה אחרת: לא "האם הקריאה נכשלה" אלא "האם ה-artefact סביר". form XObject באורך אפס, תמונה של אפס פיקסלים, עמוד של אפס בייטי תוכן, אלה הטענות שתופסות זאת
לתיקון שני חצאים והחצי השני קל לשכוח. ראשית, גרמו למימוש הריק לזרוק חריגה, כך שלכשל יש ערוץ בכלל. שנית, המירו את החריגה הזאת לתוצאת null במפעל התמונות והוסיפו בדיקות null בשני המקומות שצורכים מחוון תמונה, כי אחרת "כשל נקי" הופך ישירות ל-access violation כשעץ העמודים מפנה אל שום דבר. stub שזורק הוא שיפור רק אם הקוראים היו מוכנים לכשל שמעולם לא היו מסוגלים לקבל קודם
צורה שתיים: נתונים ריקים, שלוש שכבות מהקריסה
תחליף ה-canvas של ה-metafile לא מילא את הממדים הפיזיים שלו. הערך הזה מחלק לתוך חישוב גאומטריה של עמוד, ולכן החישוב הפיק אפס, ולכן חישוב ה-bounding-box חילק באפס. מטפל חריגות חשוף בלע את זה, מפעל התמונות החזיר תוצאת null, וה-access violation התרחש לבסוף בעץ העמודים כשה-null נוצל. שלוש שכבות בין הגורם לתסמין, עם מטפל חריגות באמצע שמוחק את הראיות
לאותה יחידה היו עוד שני מופעים של התבנית. למחלקת ה-font היו גופי Assign ו-constructor ריקים, מה שחשוב יותר משנראה כי התכונה font של ה-canvas היא לקריאה בלבד: ההצבה אליה היא הדרך היחידה למסור גופן, ולכן מימוש ריק הופך את בחירת הגופן לבלתי יעילה בשקט והטקסט יוצא במה שברירת המחדל הייתה. וערך pixels-per-inch של אפס גרם לכל קורא שממד canvas ממדדי גופן להפיק canvas אפס-על-אפס, שמניב עמוד ריק והחזרת הצלחה
// התבנית לחפש ביחידת תחליף: שיטה שלא מזיזה
// ולא זורקת חריגה. שתיהן מתקמפלות ושתיהן
// מפיקות "הצלחה" בלי פלט
procedure TMetafileCanvasStandIn.Create(...);
begin
// בלי קריאת inherited, בלי אתחול שדות
end;
function TBitmapStandIn.LoadFromStream(Stream: TStream): Boolean;
begin
Result := True; // וה-bitmap עדיין ריק
end;
המבנה הרחב ששומר רק את התו הראשון
זה בכלל לא בעיית תחליף, אבל הוא שייך לאותו קטלוג כי התסמין רחוק מהגורם באותה מידה. מבנה מניית המדפסות הוכרז עם כל שנים עשר חברי המחרוזות שלו מוקלדים כמחוונים לתווים בני בייט אחד, בזמן שהפונקציה שממלאת אותו היא הגרסה של תווים רחבים של ה-API למנייה
גדלי המחוונים זהים, ולכן פריסת המבנה נכונה ושום דבר לא קורס. מה שקורה במקום זאת הוא שקריאת מחרוזת UTF-16 כמחרוזת בת בייט אחד נעצרת בבייט האפס הראשון, שעבור כל שם מדפסת ASCII הוא החצי הגבוה של התו השני. כל שם מדפסת חזר כתו אחד בדיוק. בהמשך, אימות השם נכשל, יצירת המדפסת נכשלה וההדפסה נכשלה עבור כל מדפסת אמיתית במחשב, ואף תסמין מהאלה אינו מצביע על הצהרת מבנה
// שגוי: גודל נכון, טיפוס איבר שגוי. בלי שגיאת קימפול, בלי קריסה,
// כל מחרוזת מקוצצת לתו אחד
type
TPrinterInfo2Wrong = record
pServerName: PAnsiChar;
pPrinterName: PAnsiChar;
// ... עשרה נוספים
end;
// נכון: מבנה *W חייב חברים רחבים מקצה לקצה
type
TPrinterInfo2W = record
pServerName: PWideChar;
pPrinterName: PWideChar;
// ... עשרה נוספים
end;
הכלל שיוצא מזה מכני וראוי ליישום בלי לחשוב: לכל מבנה Win32 ששמו מסתיים ב-W, בדקו שכל חבר מחרוזת הוא הגרסה הרחבה, שדה-שדה. ערבוב העולמות של ANSI והרחב לא מפיק לא אבחון מהדר ולא קריסה, רק קיצוץ שקט, ואותו דבר חל בהיפוך על הגרסאות של ANSI
מטפל חריגות חשוף הוא היריב האמיתי
כל אחת מהחקירות האלה הואטה על ידי אותו מבנה: מטפל שתופס הכול וממיר זאת לערך החזרה false. זה דבר סביר לכתוב סביב מפענח תמונות, כי תמונה פגומה לא צריכה להפיל משימת מסמך. זה גם מתקן למחיקת פיסת המידע היחידה שאתם צריכים
התגובה המעשית היא להפוך את המטפל לרועש באופן זמני. הצפת מחלקת החריגה, ההודעה וה-backtrace מבפנים של המטפל החשוף, תחת תנאי debug, ממירה החזרת null בלתי מוסברת לחריגה בעלת שם עם מיקום. בשניים מתוך שלושת המקרים למעלה צעד אחד זה סיים את החקירה, כי החריגה הייתה חלוקה באפס או access violation בשיטת תחליף ששמה אמר הכול
רשימת בדיקה לאימוץ נתיב תחליף
ארבעה סעיפים, בסדר התשואה שלהם. לפני קריאה למחלקה תחליפית, קראו את השיטות שאתם עומדים להשתמש בהן ואמתו שלכל אחת יש גוף אמיתי; גוף ריק אינו פרט מימוש, הוא תכונה חסרה. העדיפו תחליפים שזורקים חריגות על פני תחליפים שמחזירים ערכים ניטרליים, וצמדו לזה בדיקות null במקומות שבהם מפעל יכול עכשיו להחזיר כלום באופן לגיטימי. אמתו תכונה בבחינת ה-artefact ולא קוד ההחזרה, שכן מצב הכשל כולו כאן הוא קוד החזרה נקי על artefact ריק; פירוק ברמת בייטים של מה שמסמך מכיל באמת הוא הדרך המהירה לראות זאת, ומאמר ביקורת גודל הקובץ מכסה את הכלים האלה. וכשלתכונה אין מימוש תחליף בר קיימא, נתבו את הדגימות המושפעות אל הנתיב שכן עובד ואמרו מדוע בהערה, במקום להשאיר הדגמה שמפיקה בשקט פלט ריק
הנקודה הרחבה חלה הרבה מעבר לספרייה אחת. כל בסיס קוד עם מימוש שני מותנה, שכבת mock, מצב headless, או shim של פלטפורמה, חשוף לצורה שלוש. הסיבה שהוא מסתתר כל כך טוב היא שכל שער איכות שצוות נשען עליו בדרך כלל, קודי החזרה, קודי שגיאה, חריגות, סטטוסי יציאה, הוא ערוץ סטטוס, וצורה שלוש שומרת על כולם נקיים. רק הפלט בוגד בו. זו גם ההיגיון מאחורי בדיקת artefacts במקום סטטוסים בטיפול בקלט בלתי מהימן, המתואר במאמר ניתוח PDF בלתי מהימן, ומאחורי השוואת פלט מרונדר בין מנועים במקום לסמוך על אחד, המתואר ברינדור מרובה מנועים
PDFlibPas היא ספריית PDF מקורית ב-Object Pascal עבור Delphi, C++Builder ו-Free Pascal, והתצורה שלה שאינה VCL היא מה שמאפשר בנייני headless וחוצי toolchain; כיסוי התצורות הנוכחי מופיע בדף המוצר של losLab PDF Developer Library