Műszaki cikk

Strukturált diagnosztika logikai eredmények helyett a HotXLS-ben

Futtass egy kötegelt konverziót tízezer táblázaton egy éjszaka alatt, és reggelre három közülük False-szal tér vissza. Ez a teljes utólagos elemzés, amit egy logikai mentési eredmény ad: egy bukásszám, semmi arról, melyik fájl, melyik lap, vagy egy tucat lehetséges ok közül melyik volt a felelős. A HotXLS, a losLab natív Delphi és C++Builder komponense Excel-fájlokhoz, ezt az egyetlen bitet strukturált diagnosztikára cseréli. Az IXLSWorkbookProgress interfész feltesz egy Diagnostics listát és egy OnDiagnostic eseményt, amelyek stabil numerikus kódot, súlyossági szintet, a hibázó műveletet, és a lapot jelentik, ahol az történt, minden Open, SaveAs, és Recalculate híváshoz

Miért bukik el egy logikai mentési eredmény skálázódás közben?

Nem egyetlen elbukott fájl a probléma, amit egy logikai eredmény teremt; ezer közülük az. Amikor a SaveAs sikerességtől eltérő valamit ad vissza tízezerből három fájlnál, a következő kérdés mindig ugyanaz: ez a három újrapróbálható, vagy embert igényel? Egy jogosultsági hiba egy hálózati megosztáson nem ugyanaz az incidens, mint egy képlet, amit a számítási motor nem tud kiértékelni, és egyik sem ugyanaz, mint egy munkalap, amely csendben túllépett egy formátumkorlátot. Ha csak egy sikeres/sikertelen eredmény áll rendelkezésre, mindegyik ugyanolyan támogatási jeggyé válik, és valakinek meg kell nyitnia minden fájlt kézzel, Excelben, és bámulnia kell, amíg az ok nyilvánvalóvá nem válik. Ez a kézi triázs a valódi költsége egy logikai API-nak, és lineárisan skálázódik a köteg méretével, ami pontosan az a tulajdonság, amit nem akarsz a hibakezeléstől

Az IXLSWorkbookProgress belseje: mit hordoz egy TXLSDiagnostic

Az IXLSWorkbookProgress az az interfész, amelyet a HotXLS mind egy művelet állásának, mind annak jelentésére használ, hogy mi romlott el benne, és a két fél egy okból osztozik egy szerződésen: mindkettő olyan dolog, amit egy hosszan futó Open, SaveAs, vagy Recalculate hívásnak kommunikálnia kell anélkül, hogy kivételt dobna a művelet közepén. A haladási fél az OnProgress és az OnProgressEx, amelyek egy fázissal, egy állapottal, és egy aktuális/összesen párral váltódnak ki. A diagnosztikai fél az, amiről ez a cikk szól: egy Diagnostics tulajdonság, amely egy TXLSDiagnostics listát ad vissza, egy LastDiagnostic gyorsút a legutóbbi bejegyzéshez, és egy OnDiagnostic esemény, amely abban a pillanatban váltódik ki, amikor minden TXLSDiagnostic rekord létrejön. Minden rekord hordoz egy numerikus Code-ot, egy TXLSDiagnosticSeverity-t, a TXLSDiagnosticOperation-t, amely előállította, egy ember által olvasható Message-t, egy SheetIndex-et és SheetName-et, és egy NativeCode-ot, amely megőrzi, bármilyen alacsonyabb szintű visszatérési érték is váltotta ki a bejegyzést

var
  Book: TXLSXWorkbook;
  Diag: TXLSDiagnostic;
  I: Integer;
begin
  Book := TXLSXWorkbook.Create;
  try
    if Book.SaveAs('quarterly-report.xlsx') <> 1 then
      for I := 0 to Book.Diagnostics.Count - 1 do
      begin
        Diag := Book.Diagnostics[I];
        Writeln(Format('[%d] severity=%d sheet="%s": %s',
          [Diag.Code, Ord(Diag.Severity), Diag.SheetName, Diag.Message]));
      end;
  finally
    Book.Free;
  end;
end;

A Diagnostics ilyen olvasása már önmagában is felülmúl egy logikai eredményt, mert a Code és a SheetName egy rejtélyt konkrét, szűrhető ténnyé alakít. A TXLSDiagnostic rekord messzebbre nyúlik, mint amit ez a példa kinyomtat: a RecordId és a StreamOffset bájt-szintű törvényszéki elemzéshez létezik egy BIFF-folyamon belül, és a PartName hordozza az OOXML zip-bejegyzést, mint például a xl/worksheets/sheet3.xml, ahonnan egy probléma származott. Érdemes tudni, mielőtt eszközöket építesz köréjük: a jelenlegi kiadásban egyik beépített diagnosztikai híváshely sem tölti fel a RecordId-ot vagy a StreamOffset-ot, így mindkettő a konstruktor alapértelmezésén, -1-en marad, ami "nem alkalmazható"-t jelent, nem "nulla"-t. Kezeld a hiányukat normálisnak, ne hibaként a kezelődben

Két motor, egy alak, egy csendes különbség

A HotXLS két motort szállít ugyanennek a jelentési modellnek mögötte, egy BIFF8-homlokzatot az örökölt .xls fájlokhoz, és egy OOXML-homlokzatot az .xlsx-hez, és ezek nem teszik elérhetővé az IXLSWorkbookProgress-t azonosan. A TXLSWorkbook, a .xls-motor, formálisan megvalósítja az IXLSWorkbookProgress-t, így bárhová átadható, ahol azt az interfésztípust várják. A TXLSXWorkbook, a .xlsx-motor, ugyanazokat a Diagnostics, LastDiagnostic, OnDiagnostic, OnProgress, és OnProgressEx tagokat teszi elérhetővé azonos nevekkel és típusokkal, de egyszerű osztályként, nem annak az interfésznek a formális megvalósításaként, így önmagában nem fog kielégíteni egy IXLSWorkbookProgress paramétert. A gyakorlatban ez ritkán számít, mert a legtöbb kód egyszerre egy konkrét munkafüzet-osztály ellen dolgozik, de azt jelenti, hogy nem tudsz írni egy egyetlen segédfüggvényt IXLSWorkbookProgress-re típusozva, és felcserélhetően átadni neki bármelyik motor munkafüzet-objektumát. Az egyetlen mezőkülönbség, amely közvetlenül a formátum-felosztásból következik, a PartName: csak az XLSX-motor tölti fel, mert csak az OOXML-nek vannak zip-részei, amiket megnevezhetne

Mi teszi egy diagnosztikai kódot olyanná, amire biztonságosan elágazhatsz?

A Code mező az egyetlen része egy diagnosztikának, amelyre érdemes egy összehasonlítást keményen bekódolni; a Message nem, mert a próza pontosan az a fajta dolog, amit átfogalmaznak, újrafordítanak, vagy több részlettel bővítenek egy későbbi kiadásban anélkül, hogy bárki áttörő változásnak kezelné. A HotXLS beépített diagnosztikai kódjai már úgy olvasnak, mintha ezzel a megkülönböztetéssel a fejben terveztek volna: a mentéssel kapcsolatos kódok 1000-től 1005-ig futnak, a megnyitással kapcsolatos kódok 1100-nál és 1101-nél ülnek, a számítással kapcsolatos kódok 1200-nál és 1201-nél, és egy nem-támogatott-formátum kód 1300-nál, réseket hagyva minden sávon belül ahelyett hogy a kódok egymás után futnának mindegyiken át. Ez a térközzé teszi lehetővé, hogy egy gyártó hozzáadjon egy új mentés-idejű hibamódot, mondjuk 1006-nál, anélkül hogy újraszámozná azokat a kódokat, amelyektől a switch-utasításod már függ, és érdemes ezt keresni bármely diagnosztikai API-ban, mielőtt egy kódra illesztés mellett kötelezed el magad termelésben, nem csak ennél az egynél. Tarts meg egy alapértelmezett ágat a saját diszpécselő logikádban, bármennyire stabilnak is tűnik a számozás, mert pontosan ilyen új hibamódokat fedez fel folyamatosan egy fejlődő elemző vagy író. A NativeCode és az ExceptionClass egy réteggel a Code alatt ül arra az esetre, ha eszkalálnod kell: a NativeCode megőrzi az alapul szolgáló visszatérési értéket, közte egy HRESULT-ot egy Structured Storage hívásból, és az ExceptionClass rögzíti a Delphi kivételtípust, amikor egy be volt vonva, ami általában elég ahhoz, hogy egy pontos támogatási kérést nyiss meg teljes stack trace csatolása nélkül

A súlyosság és a művelet döntik el, mit tesz ezután a kódod

A súlyosság és a művelet az, ami egy diagnosztikát naplósorból útválasztási döntéssé alakít. A TXLSDiagnosticSeverity az Info, Warning, Error, és Fatal értékeken fut, és a TXLSDiagnosticOperation minden bejegyzést megjelöl azzal a hívással, amely előállította: Open, Save, Calculate, vagy Export. A két tengely tervezés szerint független: a xlsDiagnosticUnhandledException egyetlen fix kód, amely azzal a Operation-nel váltódik ki, amelyik hívás ténylegesen kiváltotta, így a Code arra válaszol, mi romlott el, míg az Operation külön arra válaszol, hol, ahelyett hogy külön kódra lenne szükség egy kivételhez megnyitás közben szemben eggyel mentés közben. Ez az összeállíthatóság az is, ami mechanikussá teszi az útválasztást: naplózz egy figyelmeztetést, és menj tovább, egy mentés, amelyet az Aborted jelzőn keresztül megszakítottak, tipikus példa; számolj egy hibát, és tartsd futva a köteget, egy munkalap, amely nem tudott szerializálódni, tipikus példa; állítsd meg a köteget egy fatális súlyosságnál, mert az a szint azt jelenti, hogy egy kezeletlen kivétel már letekerte a hívást, és a folytatás azzal a kockázattal jár, hogy egy félig frissített állapotból dolgozol tovább. Egy őszinte figyelmeztetés: az Info létezik a felsorolásban mint az az alapértelmezés, amellyel egy friss TXLSDiagnostic kezdődik, de a mai HotXLS-kiadásba beépített minden diagnosztikai híváshely csak valaha Warning-ot, Error-t, vagy Fatal-t vált ki; az Info jövőbeli használatra van fenntartva, nem valami, amit a motor ma kibocsát

// same Diagnostics loop as above, routed by severity instead of printed flat:
for I := 0 to Book.Diagnostics.Count - 1 do
begin
  Diag := Book.Diagnostics[I];
  case Diag.Severity of
    xlsDiagnosticWarning:
      Writeln(Format('WARN  [%d] %s', [Diag.Code, Diag.Message]));
    xlsDiagnosticError:
      begin
        Writeln(Format('ERROR [%d] %s (sheet %s, native %d)',
          [Diag.Code, Diag.Message, Diag.SheetName, Diag.NativeCode]));
        Inc(FailedSheetCount);
      end;
    xlsDiagnosticFatal:
      raise Exception.CreateFmt('Fatal HotXLS diagnostic %d: %s', [Diag.Code, Diag.Message]);
  end;
end;

Az OnDiagnostic bekötése egy kötegelt csővezetékbe

A Diagnostics lekérdezése minden hívás után egyetlen fájlnál működik; leáll működni, amint visszatérsz ahhoz az éjszakai kötegelt feladathoz tízezer fájllal, mert a Diagnostics minden Open, SaveAs, és Recalculate hívás elején törlődik. Olvasd be a harmadik fájl után egy ciklusban, és csak a harmadik fájl diagnosztikáit látod; bármit is jelentett az első kettő, az már eltűnt. Az OnDiagnostic ezt úgy oldja meg, hogy a gyűjteményt streammé alakítja: iratkozz fel egyszer a ciklus indulása előtt, és ugyanaz a kezelő váltódik ki minden fájlnál, sorrendben, a fájlnévvel még hatókörben egy példánymezőn keresztül

type
  TBatchConverter = class
  private
    FCurrentFile: string;
    FFailedFiles: TStringList;
    procedure HandleDiagnostic(Sender: TObject; Diagnostic: TXLSDiagnostic);
  end;

procedure TBatchConverter.HandleDiagnostic(Sender: TObject; Diagnostic: TXLSDiagnostic);
begin
  if Diagnostic.Severity >= xlsDiagnosticError then
    FFailedFiles.Add(Format('%s: [%d] %s (sheet %s)',
      [FCurrentFile, Diagnostic.Code, Diagnostic.Message, Diagnostic.SheetName]));
end;

// inside the batch loop:
Book.OnDiagnostic := HandleDiagnostic;
for I := 0 to FileNames.Count - 1 do
begin
  FCurrentFile := FileNames[I];
  if Book.Open(FCurrentFile) = 1 then
    Book.SaveAs(ChangeFileExt(FCurrentFile, '.xlsx'));
end;

Mibe kerül ténylegesen a callback

Az OnDiagnostic olcsó strukturális okból: csak akkor váltódik ki, amikor már valami elromlott, és a romlás ritka a cellák, sorok, vagy munkalapok számához képest, amit egy munkafüzet tart. Vesd ezt össze az OnProgress-szal és az OnProgressEx-szel, amelyek rutinszerű haladást jelentenek, és a hívási gyakoriság köré kellett tervezni őket kezdettől fogva. A HotXLS munkalap-szintű haladást lapokonként egyszer vált ki Open és SaveAs közben, nem cellánként vagy soronként egyszer, ami az, ami alacsonyan tartja a hívásonkénti terhelést még milliós cellaszámú munkafüzeteknél is; a Recalculate továbbmegy, és a saját haladási eseményét nagyjából a függőségi gráf minden négy százalékához fojtja, így egy teljes újraszámítás egy szívverést ad neked, ahelyett hogy elárasztaná a UI-száladat eseményekkel. A diagnosztikának semmi ilyen fojtásra nem volt szüksége, mert az eseményszám a tényleges problémák számához van korlátozva, nem a fájl méretéhez

Az egyetlen hely, ahol a teljesítmény még mindig tőled függ, magán a kezelőn belül van. Az OnDiagnostic szinkronban váltódik ki, azon a szálon, amely az Open-t, a SaveAs-t, vagy a Recalculate-et futtatja, így egy kezelő, amely blokkol, egy szinkron írás egy távoli naplózási szolgáltatásba például, annak a hívásnak a valós idejű futásidejének részévé válik. Egyetlen fájlnál ez láthatatlan. Egy tízezer-fájlos kötegen megszorozva ez a különbség egy feladat között, amely egy éjszaka alatt befejeződik, és egy között, amely még ebédkor is fut, így pufferld, amit a kezelőnek tennie kell, és aszinkron módon öntsd ki, ahelyett hogy a lassú részt beágyaznád

A strukturált diagnosztika pontosan ott a legértékesebb, ahol egy logikai eredmény a leggyengébb, olyan munkafolyamatokban, amelyek sok fájlt érintenek, nem egyet. Egy munkafüzet-auditáló és -konvertáló csővezeték a legvilágosabb példa: ahelyett hogy egy puszta siker/bukás eredményt rögzítenél fájlonként, csatold minden fájl Diagnostics listáját az audit-rekordjához, és a jelentés nemcsak azt mondja meg, mi bukott el, hanem azt is, miért, ami nagyrészt az, amit a munkafüzet-audit és -konverziós munkapad építéséről szóló cikkünk eleve el próbál érni. A haladás és a diagnosztika ugyanezen párosítása egy olyan munkafolyamatban is helyet kap, amely már önmagáért igényel haladásjelentést, ez pontosan az a terület, amit a HotXLS nagy munkafüzetekhez tartozó teljesítményéről szóló útmutatónk tárgyal, ahol egy hosszú Open vagy SaveAs hívás elég gyakori ahhoz, hogy az OnProgress már be legyen kötve, és az OnDiagnostic egy természetes, szinte ingyenes kiegészítés legyen mellette

Ehhez semmi nem igényli az Excel telepítését bárhol a csővezetékben, és semmi nem igényli egy generikus kivétel elkapását és találgatást arról, mit jelentett. Az IXLSWorkbookProgress és a Diagnostics, LastDiagnostic, és OnDiagnostic tagjai a Delphihez és C++Builderhez készült szabványos HotXLS Komponens részei, a teljes diagnosztikai kód-referenciával, és az Open, SaveAs, és Recalculate felület többi részével együtt, amit ez a cikk bejárt