Když knihovna pro Delphi získá konfiguraci sestavení bez vizuálního frameworku, náhradní třídy jsou místem, kde žijí chyby. Ne platforma, ne kompilátor: zástupci. PDFlibPas má grafickou vrstvu poskytující ekvivalenty bitmapy, plátna, písma, metasouboru a tiskárny pro sestavení bez VCL a port na Free Pascal vynesl na povrch každý režim selhání, jakého zástupce může nabýt. Čistě se řadí podle ceny diagnostiky a toto pořadí je opačné k tomu, co naznačuje intuice
Zástupce, který vyhazuje výjimku, je levný na odhalení; výjimka jmenuje metodu. Zástupce vracející prázdná data je drahý, protože se selhání objevuje několik vrstev od své příčiny. Zástupce vracející úspěch je horší než všechno, protože návratový kód je platný, chybový kód je nula, žádná výjimka není vyhozena a jediným důkazem, že se něco pokazilo, jsou bajty, které vyšly
Podoba třetí: platný identifikátor obrázku nad prázdným XObjectem
Vektorový konvertor metasouborů bylo prázdné tělo procedury v konfiguraci bez VCL. Všechno nad ním fungovalo dál. Vstupní body importu EMF a vstupní bod zachytávání plátna doběhly do konce a vrátily legální identifikátor obrázku, který volající poté umístil na stránku. Do souboru dopadl form XObject s délkou obsahu nula. Stránka se vykreslila bílá
Nic nehlásilo problém a to platí i pro vlastní demonstrační program knihovny pro tuto funkci, který vykreslil prázdnou stránku a nevšiml si to. Nebyla tu žádná selhávající návratová hodnota ke kontrole, protože sekvence volání skutečně všechny uspěly; jediné, co bylo špatně, byla velikost produkovaného streamu. Diagnostika této třídy defektů znamená položit jinou otázku: ne „selhalo volání“, ale „je artefakt věrohodný“. Form XObject délky nula, obrázek o nula pixelech, stránka o nula bajtech obsahu — to jsou aserce, které to chytí
Oprava má dvě poloviny a ta druhá se snadno zapomene. Nejprve donutte prázdnou implementaci vyhazovat výjimku, aby selhání mělo vůbec kanál. Za druhé převeďte tuto výjimku na nulový výsledek ve továrně obrázků a přidejte kontroly null na dvou místech, která konzumují identifikátor obrázku, protože jinak se „čisté selhání“ rovnou mění v access violation, když strom stránek dereferencuje nic. Stub, který vyhazuje, je zlepšením jen tehdy, pokud byli volající připraveni na selhání, které jim dříve nikdy nebylo možno doručit
Podoba druhá: prázdná data, tři vrstvy od pádu
Zástupce plátna metasouboru nevyplnil své fyzické rozměry. Tato hodnota se dělí do výpočtu geometrie stránky, takže výpočet produkoval nulu, a výpočet ohraničujícího rámečku dělil nulou. Holý obslužný blok výjimky to spolkl, továrna obrázků vrátila nulový výsledek a access violation nakonec nastala ve stromu stránek při použití nuly. Tři vrstvy mezi příčinou a symptomem s obsluhou výjimky uprostřed, která maže důkazy
Tatáž jednotka měla dvě další instance vzoru. Třída písma měla prázdná těla Assign a konstruktoru, což je důležitější, než vypadá, protože vlastnost písma plátna je jen pro čtení: přiřazení do ní je jediný způsob, jak předat písmo, takže prázdná implementace dělá výběr písma potichu neúčinným a text vychází v tom, co bylo výchozí. A hodnota pixelů na palec rovna nule donutila každého volajícího, který rozměřuje plátno podle metrik písma, produkovat plátno nula krát nula, což dává prázdnou stránku a návrat úspěchu
// Vzor, který hledat v náhradní unitě: metoda, která ani
// nevyhazuje výjimku, ani nedělá nic. Obojí se zkompiluje a obojí
// produkuje „úspěch“ bez výstupu
procedure TMetafileCanvasStandIn.Create(...);
begin
// žádné volání inherited, žádná inicializace polí
end;
function TBitmapStandIn.LoadFromStream(Stream: TStream): Boolean;
begin
Result := True; // a bitmapa je stále prázdná
end;
Široká struktura, která ponechá jen první znak
Toto není vůbec problém zástupce, ale patří do téhož katalogu, protože symptom je stejně daleko od příčiny. Struktura enumerace tiskáren byla deklarována se všemi dvanácti členy řetězců typovanými jako ukazatele na jednobajtové znaky, zatímco funkce, která ji plní, je variantou enumer API pro široké znaky
Velikosti ukazatelů jsou identické, takže rozložení struktury je správné a nic nepadá. Místo toho se stane, že čtení řetězce UTF-16 jako jednobajtového řetězce zastane u prvního nulového bajtu, který je pro jakýkoli název tiskárny ASCII horní polovinou druhého znaku. Každý název tiskárny se vrátil jako přesně jeden znak. Níže v toku selhala validace názvů, vytváření tiskáren selhalo a tisk selhal u každé skutečné tiskárny v počítači a žádný z těch symptomů neukazuje na deklaraci struktury
// Špatně: správná velikost, špatný typ prvku. Žádná chyba kompilace,
// žádný pád, každý řetězec zkrácen na jeden znak
type
TPrinterInfo2Wrong = record
pServerName: PAnsiChar;
pPrinterName: PAnsiChar;
// ... dalších deset
end;
// Správně: struktura *W má široké členy všude
type
TPrinterInfo2W = record
pServerName: PWideChar;
pPrinterName: PWideChar;
// ... dalších deset
end;
Pravidlo z toho vzešlé je mechanické a stojí za aplikaci bez přemýšlení: u jakékoli struktury Win32, jejíž název končí na W, ověřte, že každý člen řetězce je široká varianta, pole po poli. Míchání světů ANSI a širokých neprodukuje ani diagnostiku kompilátoru, ani pád, pouze tiché zkrácení, a totéž platí obráceně pro varianty ANSI
Holý handler výjimek je skutečným protivníkem
Každé z těchto šetření bylo zpomleno týmž konstruktem: handler, který chytá všechno a převádí to na nepravdivou návratovou hodnotu. Je to rozumná věc napsat kolem dekodéru obrázků, protože poškozený obrázek by neměl složit dokumentovou úlohu. Je to také nástroj k vymazání té jediné informace, kterou potřebujete
Praktickou reakcí je učinit handler dočasně hlasitým. Vypsání třídy výjimky, zprávy a backtrace zevnitř holého handleru pod ladicí podmínkou mění nevysvětlený nulový návrat na pojmenovanou výjimku s místem. Ve dvou ze tří výše uvedených případů tento jediný krok šetření ukončil, protože výjimkou bylo dělení nulou nebo access violation v metodě zástupce, jejíž název řekl všechno
Kontrolní seznam pro přijetí cesty zástupce
Čtyři položky, v pořadí, v jakém se vyplácejí. Před voláním do náhradní třídy si přečtěte metody, které hodláte použít, a ověřte, že každá má skutečné tělo; prázdné tělo není detail implementace, je to chybějící funkce. Dávejte přednost zástupcům, kteří vyhazují, před zástupci vracejícími neutrální hodnoty, a spojte to s kontrolami null na místech, kde může továrna nyní legitimně nevrátit nic. Ověřujte funkci zkoumáním artefaktu, nikoli návratového kódu, protože celý režim selhání zde je čistý návratový kód nad prázdným artefaktem; bajtové rozčlenění toho, co dokument skutečně obsahuje, je nejrychlejší cesta k vidění, a článek o auditu velikosti souboru pokrývá toto vybavení. A když funkce nemá životaschopnou náhradní implementaci, směřujte postižené vzorky na cestu, která funguje, a řekněte proč v komentáři, namísto ponechání demonstrace, která potichu produkuje prázdný výstup
Širší bod se uplatňuje daleko za hranicemi jedné knihovny. Jakákoli kódová základna s podmíněnou druhou implementací, mock vrstvou, headless režimem, platformovým shimem, je vystavena podobě třetí. Důvod, proč se tak dobře schovává, je ten, že každá brána kvality, na kterou se tým obvykle spoléhá — návratové kódy, chybové kódy, výjimky, exit statusy — je kanál stavu a podoba třetí je všechny drží čisté. Prozradí ji jen výstup. To je také úvaha za kontrolou artefaktů namísto stavů při zpracování nedůvěryhodného vstupu, popsaná v článku o parsování nedůvěryhodných PDF, a za porovnáváním vykresleného výstupu napříč enginy namísto důvěry jednomu, popsaná v víceenginovém vykreslování
PDFlibPas je nativní PDF knihovna v Object Pascalu pro Delphi, C++Builder a Free Pascal a její konfigurace bez VCL je to, co umožňuje headless a cross-toolchain sestavení; aktuální pokrytí konfigurací uvádí stránka produktu losLab PDF Developer Library