Keď knižnica pre Delphi získa zostavovaciu konfiguráciu bez vizuálneho frameworku, náhradné triedy sú miestom, kde bývajú chyby. Nie platforma, nie kompilátor: náhradníci. PDFlibPas má grafickú vrstvu, ktorá poskytuje ekvivalenty bitmapy, canvasu, fontu, metafilu a tlačiarne pre buildy bez VCL, a jej prenos do Free Pascalu vyniesol na povrch každý režim zlyhania, ktorý náhradník môže mať. Triedia sa prehľadne podľa ceny diagnostiky a toto poradie je opakom toho, čo naznačuje intuícia
Náhradník, ktorý vyhadzuje výnimky, je lacný na nájdenie; výnimka menuje metódu. Náhradník, ktorý vracia prázdne dáta, je drahý, pretože zlyhanie sa objaví niekoľko vrstiev od svojej príčiny. Náhradník, ktorý vracia úspech, je najhorší zo všetkých, pretože návratový kód je platný, chybový kód je nula, žiadna výnimka sa nezvýši a jediným dôkazom, že niečo sa pokazilo, sú bajty, ktoré vyšli
Tvar tri: platný identifikátor obrazu nad prázdnym XObject
Vektorový konvertor metafilu bolo prázdne telo procedúry v konfigurácii non-VCL. Všetko nad ním fungovalo ďalej. Vstupné body importu EMF a vstupný bod zachytávania canvasu bežali do konca a vrátili legálny identifikátor obrazu, ktorý volajúci potom umiestnil na stránku. Do súboru pristál form XObject s dĺžkou obsahu nula. Stránka sa vyrenderovala biela
Nič nehlásilo problém a to zahŕňa aj vlastnú demonštračnú aplikáciu knižnice pre túto funkciu, ktorá nakreslila prázdnu stránku a nevšimla si to. Nebola tu žiadna zlyhávajúca návratová hodnota na skontrolovanie, pretože sekvencia volaní naozaj všetky uspela; jediné, čo bolo zlé, bola veľkosť vyrobeného streamu. Diagnostikovanie tejto triedy defektov znamená položiť si inú otázku: nie „zlyhalo volanie“, ale „je artefakt plauzibilný“. Form XObject s dĺžkou nula, obraz s nulou pixelov, stránka s nulou bajtov obsahu — toto sú asercie, ktoré to zachytia
Oprava má dve polovice a druhá polovica sa ľahko zabúda. Najprv prinúťte prázdnu implementáciu vyhadzovať výnimky, aby zlyhanie vôbec malo kanál. Potom preveďte tú výnimku na null výsledok vo fabrike obrazov a pridajte kontroly null na dvoch miestach, ktoré konzumujú identifikátor obrazu, pretože inak sa „čisté zlyhanie“ rovno zmení na access violation, keď strom stránok dereferencuje nič. Stub, ktorý hádže výnimky, je zlepšením len vtedy, keď volajúci boli pripravení na zlyhanie, ktoré predtým nikdy neboli schopní prijať
Tvar dva: prázdne dáta, tri vrstvy od pádu
Náhradník metafilového canvasu nevyplnil svoje fyzické rozmery. Tá hodnota delí do výpočtu geometrie stránky, takže výpočet vyprodukoval nulu a výpočet ohraničujúceho rámca delil nulou. Holý handler výnimiek to prehltil, fabriky obrazov vrátila null výsledok a access violation sa konečne stal v strome stránok, keď sa null použil. Tri vrstvy medzi príčinou a príznakom, s handlerom výnimiek uprostred, ktorý maže dôkazy
Tá istá jednotka mala ďalšie dva prípady tohto vzoru. Trieda fontu mala prázdne telá Assign a konštruktora, čo je dôležitejšie, než vyzerá, pretože vlastnosť fontu canvasu je read-only: priradenie do nej je jediný spôsob, ako doručiť font, takže prázdna implementácia robí výber fontu potichu neúčinným a text vychádza v tom, čo bolo predvolené. A hodnota pixels-per-inch nula spôsobila, že každý volajúci, ktorý dimenzuje canvas z metrík fontu, vyprodukoval canvas nula krát nula, čo dáva prázdnu stránku a návrat úspechu
// Tvar, ktorý treba hľadať v jednotke náhradníka: metóda, ktorá ani
// nevyhadzuje výnimky, ani nič nespraví. Obe sa skompilujú a obe
// produkujú „úspech“ bez výstupu
procedure TMetafileCanvasStandIn.Create(...);
begin
// žiadne volanie inherited, žiadna inicializácia polí
end;
function TBitmapStandIn.LoadFromStream(Stream: TStream): Boolean;
begin
Result := True; // a bitmapa je stále prázdna
end;
Široká štruktúra, ktorá si ponechá len prvý znak
Toto vôbec nie je problém náhradníka, ale patrí do rovnakého katalógu, pretože príznak je rovnako ďaleko od príčiny. Štruktúra enumerácie tlačiarní bola deklarovaná so všetkými dvanástimi reťazcovými členmi typovanými ako ukazovatele na jednobajtové znaky, zatiaľ čo funkcia, ktorá ju vyplňuje, je širokoznaková varianta enumeráčného API
Veľkosti ukazovateľov sú identické, takže rozloženie štruktúry je správne a nič sa nezrúti. Namiesto toho čítanie UTF-16 reťazca ako jednobajtového reťazca sa zastaví na prvom nulovom bajte, ktorý je pre akékoľvek ASCII meno tlačiarne hornou polovicou druhého znaku. Každé meno tlačiarne sa vrátilo presne ako jeden znak. Po prúde zlyhala validácia mien, vytváranie tlačiarní a tlač pre každú skutočnú tlačiareň na stroji a ani jeden z týchto príznakov neukazuje na deklaráciu štruktúry
// Zle: správna veľkosť, nesprávny typ prvku. Žiadna chyba kompilácie,
// žiadny pád, každý reťazec skrátený na jeden znak
type
TPrinterInfo2Wrong = record
pServerName: PAnsiChar;
pPrinterName: PAnsiChar;
// ... ďalších desať
end;
// Správne: štruktúra *W má široké členy od začiatku do konca
type
TPrinterInfo2W = record
pServerName: PWideChar;
pPrinterName: PWideChar;
// ... ďalších desať
end;
Pravidlo, ktoré z toho vychádza, je mechanické a stojí za uplatnenie bez premýšľania: pri každej Win32 štruktúre, ktorej meno končí na W, overte, že každý reťazcový člen je široká varianta, pole po poli. Miešanie svetov ANSI a wide neprináša ani kompilátorovú diagnostiku, ani pád, len tiché skracovanie a to isté platí naopak pre varianty ANSI
Holý handler výnimiek je skutočným protivníkom
Každé z týchto šetrení spomalila tá istá konštrukcia: handler, ktorý chytí všetko a prevedie to na návratovú hodnotu false. Je to rozumná vec napísať okolo dekódera obrazov, keďže poškodený obraz nemá zrútiť dokumentovú úlohu. Je to aj zariadenie na zmazanie tej jedinej informácie, ktorú potrebujete
Praktická reakcia je spraviť handler dočasne hlučným. Vypísanie triedy výnimky, správy a backtrace zvnútra holého handlera pod debug podmienkou premení nevysvetlený null návrat na pomenovanú výnimku s lokalitou. V dvoch z troch prípadov vyššie tento jediný krok šetrenie ukončil, pretože výnimka bola delenie nulou alebo access violation v metóde náhradníka, ktorej meno povedalo všetko
Kontrolný zoznam pri osvojovaní cesty náhradníka
Štyri položky, v poradí, v akom sa vyplácajú. Pred volaním do náhradnej triedy si prečítajte metódy, ktoré sa chystáte použiť, a potvrďte, že každá má reálne telo; prázdne telo nie je detail implementácie, je to chýbajúca funkcia. Uprednostnite náhradníkov, ktorí vyhadzujú výnimky, pred náhradníkmi vracajúcimi neutrálne hodnoty, a spojte to s kontrolami null na miestach, kde teraz fabrika môže legitímne nevrátiť nič. Overujte funkciu skúmaním artefaktu, nie návratového kódu, keďže celý režim zlyhania tu je čistý návratový kód nad prázdnym artefaktom; rozklad na úrovni bajtov toho, čo dokument naozaj obsahuje, je najrýchlejší spôsob, ako to vidieť, a článok o audite veľkosti súboru pokrýva toto náradie. A keď funkcia nemá životaschopnú náhradnú implementáciu, smerujte dotknuté vzorky na cestu, ktorá funguje, a povedzte prečo v komentári, namiesto ponechania demonštrácie, ktorá potichu produkuje prázdny výstup
Širší bod platí ďaleko za hranicami jednej knižnice. Každá kódová báza s podmienenou druhou implementáciou, mock vrstvou, headless režimom či platformovým shimom je vystavená tvaru tri. Dôvod, prečo sa tak dobre skrýva, je, že každá brána kvality, na ktorú sa tím zvyčajne spolieha — návratové kódy, chybové kódy, výnimky, exit statusy — je kanál statusov a tvar tri ich všetky udržuje čisté. Zradí ho len výstup. To je aj uvažovanie za kontrolou artefaktov namiesto statusov pri spracovaní nedôveryhodného vstupu, popísané v článku o parsovaní nedôveryhodných PDF, a za porovnávaním vyrenderovaného výstupu naprieč enginmi namiesto dôvery jednému, popísané v renderovaní s viacerými enginmi
PDFlibPas je natívna PDF knižnica v Object Pascale pre Delphi, C++Builder a Free Pascal a jej konfigurácia non-VCL je to, čo umožňuje headless a cross-toolchain buildy; aktuálne pokrytie konfigurácií je uvedené na produktovej stránke losLab PDF Developer Library