Odborný článok

Auditovanie cache vzorcov Excel s HotXLS Deep Recalc

HotXLS odpovedá na otázku, ktorú si každý tabuľkový pipeline skôr či neskôr musí položiť: či čísla uložené v zošite ešte sedia so vzorcami, ktoré ich vyprodukovali. CalculateAndVerify prepočíta celý graf závislostí do izolovaného overlayu, porovná každý výsledok s cache hodnotou, ktorá už je v bunke, a nahlási nesúhlasy. Defaultne nemení nič

Dôvod, prečo to záleží, je ten, že tabuľkový súbor ukladá dve veci na bunku so vzorcom: vzorec a poslednú hodnotu, ktorú niekto preň vypočítal. Excel ich drží v syncu. Všetko ostatné na svete nemusí. Súbor, ktorý prešiel staršou knižnicou, čiastočným prepočtom, ručne editovanou XML časťou alebo nástrojom, ktorý zapisoval hodnoty bez prepočítania, vám s radosťou predloží súhrn, ktorý už nevyplýva zo svojich vstupov, a nič vo formáte súboru to neoznačí

Prečo je cache hodnota nesúhlasíaca so svojím vzorcom taká nebezpečná?

Pretože je neviditeľná v každej obyčajnej čítacej ceste. Otvorte súbor v prehliadači, prečítajte bunku cez API, exportujte do CSV alebo PDF a dostanete cache číslo. Vzorec je priamo tam v tej istej bunke a nikto ich neporovná. Nesúhlas vypláva na povrch až vtedy, keď niekto otvorí zošit v Exceli, ktorý sa podľa väčšiny nastavení prepočítava pri načítaní, a zrazu report, ktorý minulý kvartál podpísali, ukazuje iné súčty

Audit existuje preto, aby z toho porovnania spravil zámernú, naplánovanú operáciu, nie nehodu. Je to tabuľková podoba overenia kontrolného súčtu: lacné na spustenie v príjmovom pipeline a jediná vec, ktorá z tichého problému integrity dát urobí report, s ktorým sa dá niečo robiť

var
  Book: TXLSWorkbook;
  Options: TXLSRecalcAuditOptions;
  Report: TXLSCalculationAuditReport;
  I: Integer;
begin
  Book := TXLSWorkbook.Create(nil);
  try
    Book.LoadFromFile('quarterly-close.xls');
    Options := TXLSRecalcAuditOptions.Default;
    Options.MaxIssues := 500;
    Report := Book.CalculateAndVerify(Options);
    try
      for I := 0 to Report.Count - 1 do
        if Report[I].Kind = xlcaiCacheMismatch then
          Writeln(Report[I].SheetName, '!',
                  Report[I].Row, ':', Report[I].Col, '  ',
                  Report[I].Formula,
                  '  cached=', VarToStr(Report[I].Actual),
                  '  recomputed=', VarToStr(Report[I].Expected));
      if Report.Truncated then
        Writeln('issue budget reached, raise MaxIssues');
    finally
      Report.Free;
    end;
  finally
    Book.Free;
  end;
end;

Existujú tri overloady a odpovedajú na tri odlišné otázky. Bezparametrový CalculateAndVerify vráti počet nesúhlasov, čo je všetko, čo zdravotná kontrola potrebuje. Overload s out poľom nesúhlasov vám dá bunky. Overload berúci TXLSRecalcAuditOptions vráti plný TXLSCalculationAuditReport a po tom siahnite, keď potrebujete vedieť nielen to, že hodnota nesúhlasí, ale aj prečo audit nemohol niečo vyhodnotiť

Overlay a prečo audit nezapisuje

Každá prepočítaná hodnota dopadne do overlayu, nie do cache buniek, a overlay sa vstrekuje na samom začiatku callbacku čítania buniek v oboch engineoch zošita. Toto umiestnenie je to, čo robí audit sebakonzistentným: keď sa prepočíta B1 a C1 závisí na B1, C1 vidí hodnotu z tohto auditného prechodu, nie zastaralú cache. Bez toho by sa jediná chyba upstreamu nahlásila raz a potom absorbovala a každá downstream bunka by pôsobila, že súhlasí so zlým vstupom

Bunky, ktorých prepočítaná hodnota sedí s cache, do overlayu vôbec nevstupujú. To nie je mikrooptimalizácia, to je to, čo drží audit cenovo dostupný. Čistý zošit so stotisíc vzorcami vykoná nulové zápisy do overlayu a prechod zostáva v rámci rozpočtu 1,35x oproti plnému prepočtu, čo je rozdiel medzi vecou, ktorú môžete spustiť pri každom príjme, a vecou, ktorú spustíte raz za kvartál

Pipeline auditu deep recalc HotXLS: zošit sa načíta s cache nedotknutými, každý uzol závislostí sa označí ako dirty a vyhodnotí raz v topologickom poradí, prepočítané hodnoty dopadnú do izolovaného overlayu, ktorý callback čítania buniek konzultuje ako prvý v oboch engineoch, výsledky sa porovnajú s cache hodnotami, klasifikujú cez CalculateAndVerify do TXLSCalculationAuditReport a na disk sa nezapisuje nič
Prepočítané hodnoty dopadnú do overlayu pred callbackom čítania buniek, sediace bunky sa ho nikdy nedotknú a zošit na disku zostáva nedotknutý, dokiaľ ApplyResults nepotvrdí úplne čistý prechod

Vyhodnocovanie nasleduje sériové topologické poradie odvodené z grafu závislostí, pričom každý uzol sa najprv označí ako dirty, takže každá bunka sa vypočíta presne raz po svojich vstupoch. Ak chcete inkrementálnu mechaniku, ktorá drží živý zošit aktuálny namiesto auditovania uloženého, to je iný mechanizmus, popísaný v článku inkrementálne prepočítavanie a graf závislostí

Zlyhania sú klasifikované, nie hŕobené dokopy

Bunka, ktorú audit nedokáže vyhodnotiť, nie je to isté zistenie ako bunka, ktorej hodnota nesúhlasí, a TXLSCalculationAuditIssueKind drží kategórie rozdelené. xlcaiCacheMismatch je nesúhlas hodnôt. xlcaiMissingFunction a xlcaiMissingName hovoria, že vyhodnocovač stretol niečo, čo neimplementuje alebo nevie resolveovať. xlcaiUnsupportedArguments kryje tvary argumentov mimo podporovanej podmnožiny. xlcaiExternalReferenceDenied a xlcaiExternalReferenceMissing oddeľujú politické zamietnutie od chýbajúceho zošitu. xlcaiCircularReference, xlcaiDataTableSkipped, xlcaiParseFailure, xlcaiCancelled a xlcaiInternalFailure sadu dopĺňajú

Klasifikácia zistení auditu HotXLS: TXLSCalculationAuditIssueKind oddeľuje nesúhlas hodnôt hlásený ako xlcaiCacheMismatch od druhov zlyhania vyhodnotenia ako xlcaiMissingFunction, xlcaiMissingName, xlcaiUnsupportedArguments, dvojice xlcaiExternalReferenceDenied verzus xlcaiExternalReferenceMissing a xlcaiCircularReference, kým kladný chybový kód Excel sa počíta ako výsledok, nie zlyhanie
Jeden druh hlási nesúhlas hodnôt a ostatné hlásia, prečo vyhodnocovač nemohol posúdiť bunku; chybová hodnota Excel je vypočítaný výsledok, takže zámerné chybové bunky produkujú nulové zistenia

Jedno rozlíšenie stojí za vyslovenie, lebo prevracia bežný predpoklad. Kladný chybový kód Excel je výsledok, nie zlyhanie. Bunka, ktorá legitímne vyhodnotí na #DIV/0!, vypočítala správne, takže audit uloží túto chybu do overlayu a porovná ju s cache ako ktorúkoľvek inú hodnotu. Zošit plný zámerných chybových buniek produkuje nulové zistenia a zošit, kde chyba od času cachovania hodnôt pribudla alebo ubudla, produkuje presne tie zistenia, ktoré chcete

Cirkulárne referencie dostávajú vlastné ošetrenie. Uzly v cykle nikdy nevstupujú do topologického poradia, takže každý sa hlási osobitne ako xlcaiCircularReference a audit nespúšťa iteratívny solver. To je zámerná read-only zmluva: to, či je iterácia zapnutá, ovplyvňuje, ako sa má kód výsledku interpretovať, nie to, čo audit robí. Mechanika iteratívneho vyhodnocovania je popísaná osobitne v článku iteratívny výpočet a cirkulárne referencie

Čítanie reťaza zlyhaní

Keď vzorec nedokáže vyhodnotiť, poznať, ktorá bunka zlyhala, nestačí, pretože zlyhanie býva tri úrovne hlboko v reťaze referencií. Každé zistenie preto nesie reťazec Stack renderovaný najvnejším rámcom na prvom mieste, v tvare Sheet1!A1 > Sheet1!B2 > Data!C7, takže report ukazuje na bunku, ktorá reálne prerušila, nie na bunku, ktorú ste náhodou pozerali

Záznamník je ohraničený. MaxStackFrames má default 64 s minimom 8 a najhlbší zlyhávajúci reťaz je ten, ktorý sa ponechá: vnútorný rámec zaznamená reťaz, keď zlyhanie vznikne v ňom, a vonkajšie rámce sa rozbaľujúce potom ho neprepíšu. Ak ktorýkoľvek reťaz prekročil rozpočet, nastaví sa Report.StackTruncated, čo vám povie rozdiel medzi krátkym reťazom a reťazom, ktorý ste nevideli celý

Reťaz zlyhaní auditu HotXLS: keď zlyhá vzorec tri referencie hlboko, Stack renderuje najvnejší rámec na prvom mieste, Sheet1!A1 potom Sheet1!B2 potom Data!C7, najvnútornejší rámec zaznamená reťaz a vonkajšie rámce sa rozbaľujúce ho neprepíšu, MaxStackFrames má default 64 s minimom 8 a Report.StackTruncated označí reťaz, ktorý ste nevideli celý
Stack renderuje najvnejší rámec na prvom mieste, takže report ukazuje na bunku, ktorá reálne prerušila, najhlbší zlyhávajúci reťaz je ten ponechaný a StackTruncated oddeľuje krátke reťazy od orezaných
// Defaultne read-only. ApplyResults potvrdí overlay až po úplne
// úspešnom audite, pod ochranou zápisu, ktorá zamietne potvrdenie,
// ak sa štruktúra zošitu zmenila, kým audit bežal
Options := TXLSRecalcAuditOptions.Default;
Options.ApplyResults := True;
Options.AbsoluteTolerance := 0;     // presné porovnanie, odhalí drift
Options.RelativeTolerance := 0;
Options.OnProgress := HandleProgress;

Report := Book.CalculateAndVerify(Options);
try
  if Report.Applied then
    Book.SaveToFile('quarterly-close-repaired.xls')
  else
    Writeln('not applied: ', Report.Count, ' issues blocked the commit');
finally
  Report.Free;
end;

procedure THarness.HandleProgress(ASender: TObject;
  ACurrent, ATotal: Integer; var ACancel: Boolean);
begin
  ACancel := FUserRequestedStop;   // audit zastane na ďalšej hranici uzlov
end;

Kedy dovoliť auditu zošit opraviť?

Len keď sa audit vrátil úplne čistý od zistení triedy zlyhaní, čo je presne podmienka, ktorú vám vynucuje ApplyResults. Potvrdenie nastane po úplne úspešnom prechode, ktorý nebol zrušený, a prejde štrukturálnou ochranou: binárny engine sleduje identifikátor zmeny zošitu, OOXML engine spraví snapshot generácie štruktúry na hárok. Ak sa čokoľvek pohlo, kým audit bežal, výsledky opisujú zošit, ktorý už neexistuje, a potvrdenie sa zamietne

Všimnite si zámernú asymetriu. Cache nesúhlasy nezablokujú aplikáciu, pretože presne to je to, čo má potvrdenie opraviť. Zistenia triedy zlyhaní ju zablokujú, pretože zošit, v ktorom niektoré vzorce nebolo možné vyhodnotiť, by bol napoly opravený a napoly opravený zošit je horší než neopravený, ktorému viete, že nemáte veriť

Tolerancia je politické rozhodnutie, nie default

Predvolené porovnanie je absolútna tolerancia 1E-6 s vypnutou relatívnou toleranciou, čo zachováva klasické správanie a poticho akceptuje drift 4E-7. To býva správne: rozdiely v poradí vyhodnocovania plávajúcej čiarky medzi tým, čo súbor vyprodukovalo, a aktuálnym vyhodnocovačom vyprodukujú na dlhých súčtoch rozdiely tejto veľkosti a hlásiť ich ako zistenia integrity je šum

Nastavte obe tolerancie na nulu, keď je otázka iná, keď sa snažíte zistiť, či vyhodnocovač zmenil správanie medzi verziami, alebo či nástroj tretej strany prepisuje hodnoty jemne odlišným spôsobom. Na nule sa ten istý drift 4E-7 stane viditeľným a takisto všetko ostatné. Vyberte toleranciu podľa otázky, ktorú kladiete, a zaznamenajte voľbu vedľa reportu, pretože report bez svojej tolerancie nie je interpretovateľný

Obraz dotvárajú dve susedné schopnosti. Keď chcete vedieť, prečo jediný vzorec produkuje hodnotu, ktorú produkuje, správnym nástrojom je pohľad krok za krokom v článku tracer vyhodnocovania vzorcov. Keď zámerné chcete, aby sa cache hodnoty respektovali bez akéhokoľvek prepočtu, napríklad na príjmovej ceste, ktorá musí reprodukovať súbor presne tak, ako prišiel, tento režim popisuje článok čítanie cache hodnôt vzorcov bez prepočítavania. Audit je to, čo sedí medzi nimi: povie vám, či je dôverovanie cache bezpečné. Prichádza s HotXLS Delphi tabuľkovým komponentom pre binárny aj OOXML engine