Когато Delphi библиотека придоби конфигурация за компилация без визуалния фреймуърк, заместващите класове са мястото, където живеят дефектите; Не платформата, не компилаторът: заместителите; PDFlibPas има graphics слой, който предоставя bitmap, canvas, font, metafile и printer еквиваленти за компилации без VCL, а пренасянето му към Free Pascal извади на повърхността всеки режим на отказ, който заместител може да има; Те се подреждат чисто по цена за диагностика, а подредбата е обратната на онова, което интуицията подсказва
Заместител, който хвърля, е евтин за откриване; изключението назовава метода; Заместител, който връща празни данни, е скъп, защото отказът се появява няколко слоя далеч от причината си; Заместител, който връща успех, е най-лошият от всички, защото return кодът е валиден, error кодът е нула, няма хвърлено изключение и единственото доказателство, че нещо се е объркало, е в байтовете, които излязоха
Форма три: валиден image идентификатор върху празен XObject
Векторният metafile конвертор беше празно тяло на процедура в non-VCL конфигурацията; Всичко над него продължи да работи; EMF импортните входни точки и входната точка за canvas прихващане тичаха до край и връщаха легален image идентификатор, който извикващият после поставяше на страница; Това, което попадна във файла, беше form XObject с дължина на съдържанието нула; Страницата се изобрази бяла
Нищо не докладва проблем и това включва собствената демонстрационна програма на библиотеката за тази функция, която нарисува празна страница и не забеляза; Нямаше неуспешна return стойност за проверка, защото последователността от извиквания наистина всички успяха; единственото грешно нещо беше размерът на произведената поток; Диагностицирането на този клас дефекти означава задаване на друг въпрос: не „успя ли извикването“, а „правдоподобен ли е артефактът“; Form XObject с дължина нула, изображение с нула пиксела, страница с нула байта съдържание — това са assertion-ите, които го хващат
Поправката има две половини и втората половина е лесна за забравяне; Първо, накарайте празната имплементация да хвърля, така че отказът изобщо да има канал; Второ, превърнете това изключение в null резултат на image factory-то и добавете null проверки на двете места, които консумират image идентификатор, защото иначе „чист отказ“ се превръща направо в access violation, докато page tree-то дереференцира нищо; Заглушка, която хвърля, е подобрение само ако извикващите са били подготвени за отказ, който никога преди не са могли да получат
Форма две: празни данни, три слоя от срива
Metafile canvas заместителят не попълваше физическите си размери; Тази стойност влиза в изчисление на геометрия на страница, така че изчислението произвежда нула, така че изчислението на bounding box раздели на нула; Гол обработчик на изключения погълна това, image factory-то върна null резултат и access violation накрая се случи в page tree-то, когато null беше използван; Три слоя между причина и симптом, с обработчик на изключения по средата, изтриващ доказателствата
Същият модул имаше още два случая на образеца; Font класът имаше празни тела на Assign и конструктора, което има повече значение, отколкото изглежда, защото font свойството на canvas-а е read-only: присвояването в него е единственият начин да се достави шрифт, така че празна имплементация прави избора на шрифт мълшиво безействен и текстът излиза в каквото по подразбиране е било; И стойност pixels-per-inch нула накара всеки извикващ, който оразмерява canvas от font метрики, да произвежда нула на нула canvas, което дава празна страница и return успех
// Образецът, който да търсите в заместещ модул: метод, който нито
// хвърля, нито прави нещо. И двете се компилират и двете произвеждат
// „успех“ без изход
procedure TMetafileCanvasStandIn.Create(...);
begin
// без inherited извикване, без инициализация на полета
end;
function TBitmapStandIn.LoadFromStream(Stream: TStream): Boolean;
begin
Result := True; // а bitmap-ът все още е празен
end;
Широката структура, която запазва само първия символ
Този изобщо не е проблем на заместител, но принадлежи на същия каталог, защото симптомът е също толкова далеч от причината; Printer enumeration структурата беше декларирана с всичките ѝ дванайсет string членове типизирани като указатели към еднобайтови символи, докато функцията, която я попълва, е wide-character вариантът на enumeration API-то
Размерите на указателите са идентични, така че оформлението на структурата е правилно и нищо не се срива; Това, което се случва вместо това, е, че четенето на UTF-16 низ като еднобайтов низ спира на първия нулев байт, който за всяко ASCII принтерно име е старшата половина на втория символ; Всяко принтерно име се върна като точно един символ; Надолу по веригата валидацията на имена се провали, създаването на принтер се провали и печатането се провали за всеки реален принтер на машината и никой от тези симптоми не сочи към декларация на структура
// Грешно: правилен размер, грешен елементен тип. Няма компилаторна
// грешка, няма срив, всеки низ съкратен до един символ
type
TPrinterInfo2Wrong = record
pServerName: PAnsiChar;
pPrinterName: PAnsiChar;
// ... още десет
end;
// Правилно: *W структура има wide членове навсякъде
type
TPrinterInfo2W = record
pServerName: PWideChar;
pPrinterName: PWideChar;
// ... още десет
end;
Правилото, което излиза оттам, е механично и си заслужава да се прилага без мислене: за всяка Win32 структура, чието име завършва на W, проверете, че всеки string член е wide вариантът, поле по поле; Смесването на ANSI и wide световете не произвежда нито компилаторна диагноза, нито срив, само мълшиво съкращаване и същото важи обратно за ANSI вариантите
Гол обработчик на изключения е истинският противник
Всяко от тези разследвания беше забавяно от един и същ конструкт: обработчик, който хваща всичко и го превръща в false return стойност; Това е разумно нещо за писане около image decoder, тъй като повредено изображение не бива да събаря документна задача; То е и устройство за изтриване на единственото парче информация, от което се нуждаете
Практическият отговор е обработчикът временно да стане шумен; Извеждането на класа на изключението, съобщението и backtrace от вътрешността на голия обработчик, под debug условен израз, превръща необясним null return в назовано изключение с локация; В два от трите случая по-горе тази единствена стъпка прекрати разследването, защото изключението беше деление на нула или access violation в метод заместител, чието име казваше всичко
Контролен списък за възприемане на път с заместител
Четири точки, в реда, в който се изплащат; Преди да извикате заместещ клас, прочетете методите, които ще използвате, и потвърдете, че всеки има истинско тяло; празно тяло не е детайл на имплементацията, то е липсваща функция; Предпочитайте заместители, които хвърлят, пред заместители, които връщат неутрални стойности, и сдвоете това с null проверки на местата, където factory вече може законно да върне нищо; Верифицирайте функция, като инспектирате артефакта, не return кода, тъй като целият режим на отказ тук е чист return код върху празен артефакт; байтова разбивка на това, което документ действително съдържа, е най-бързият начин да го видите, а статията за одит на размера на файл разглежда тази инструментация; И когато функция няма жизнеспособна заместеща имплементация, насочете засегнатите примери към пътя, който работи, и кажете защо в коментар, вместо да оставите демонстрация, която мълшиво произвежда празен изход
По-широката точка се отнася далеч отвъд една библиотека; Всеки codebase с условна втора имплементация, mock слой, headless режим, platform shim, е изложен на форма три; Причината да се крие толкова добре е, че всяка качествена порта, на която един екип обикновено разчита — return кодове, error кодове, изключения, exit статуси — е статусен канал, а форма три ги пази всички чисти; Само изходът го издава; Това е и разсъждението зад проверката на артефакти, а не статуси, при обработка на ненадежден вход, описано в статията за парсване на ненадежден PDF, и зад сравняването на изобразения изход между engines, вместо да се вярва на един, описано в multi-engine изобразяване
PDFlibPas е родна Object Pascal PDF библиотека за Delphi, C++Builder и Free Pascal, а неговата non-VCL конфигурация е това, което прави headless и cross-toolchain компилации възможни; текущото покритие на конфигурации е изброено на продуктовата страница losLab PDF Developer Library