Rulați o conversie în lot peste zece mii de foi de calcul peste noapte, iar dimineața trei dintre ele revin False. Acesta este întregul post-mortem pe care vi-l oferă un rezultat boolean de salvare: un număr de eșecuri, fără nimic despre ce fișier, ce foaie, sau care dintre zeci de cauze posibile a fost responsabilă. HotXLS, componenta nativă losLab pentru Delphi și C++Builder pentru fișiere Excel, înlocuiește acel singur bit cu diagnostice structurate. Interfața IXLSWorkbookProgress expune o listă Diagnostics și un eveniment OnDiagnostic care raportează un cod numeric stabil, un nivel de severitate, operația care a eșuat, și foaia unde s-a întâmplat, pentru fiecare apel Open, SaveAs și Recalculate
De ce eșuează un rezultat boolean de salvare la scară?
Un singur fișier eșuat nu este problema pe care o creează un rezultat boolean; o mie dintre ele sunt. Când SaveAs returnează altceva decât succes pentru trei fișiere din zece mii, următoarea întrebare este întotdeauna aceeași: sunt aceste trei reîncercabile, sau au nevoie de un om? O eroare de permisiune pe un share de rețea nu este același incident ca o formulă pe care motorul de calcul nu o poate evalua, și niciunul nu este la fel ca o foaie de calcul care a depășit silențios o limită de format. Cu doar un rezultat trece/eșuează cu care să lucrați, fiecare din acestea devine un tichet de suport identic, iar cineva trebuie să deschidă fiecare fișier manual, în Excel, și să se uite la el până când cauza devine evidentă. Acea triere manuală este costul real al unui API boolean, și se scalează liniar cu dimensiunea lotului, ceea ce este exact proprietatea pe care nu o doriți de la tratarea erorilor
În interiorul IXLSWorkbookProgress: ce poartă un TXLSDiagnostic
IXLSWorkbookProgress este interfața pe care HotXLS o folosește pentru a raporta atât cum decurge o operație, cât și ce a mers prost în interiorul ei, iar cele două jumătăți împart un contract pentru un motiv: ambele sunt lucruri pe care un apel Open, SaveAs sau Recalculate de lungă durată trebuie să le comunice fără a ridica o excepție la mijlocul operației. Jumătatea de progres este OnProgress și OnProgressEx, declanșate cu o fază, o stare și o pereche curent/total. Jumătatea de diagnostice este cea despre care este acest articol: o proprietate Diagnostics care returnează o listă TXLSDiagnostics, o scurtătură LastDiagnostic pentru cea mai recentă intrare, și un eveniment OnDiagnostic care se declanșează în momentul în care este creată fiecare înregistrare TXLSDiagnostic. Fiecare înregistrare poartă un Code numeric, o TXLSDiagnosticSeverity, un TXLSDiagnosticOperation care a produs-o, un Message lizibil de om, un SheetIndex și SheetName, și un NativeCode care păstrează orice valoare de retur de nivel inferior a declanșat intrarea
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;
Citirea lui Diagnostics așa deja depășește un rezultat boolean de la sine, pentru că Code și SheetName transformă un mister într-un fapt specific, filtrabil. Înregistrarea TXLSDiagnostic ajunge mai departe decât ce afișează acest exemplu: RecordId și StreamOffset există pentru criminalistică la nivel de octet în interiorul unui flux BIFF, iar PartName conține intrarea zip OOXML, precum xl/worksheets/sheet3.xml, de unde a provenit o problemă. Merită cunoscut înainte de a construi unelte în jurul lor: în lansarea curentă niciun punct de apel de diagnostic încorporat nu populează RecordId sau StreamOffset, așa că ambele rămân la valoarea implicită de constructor de -1, însemnând „nu se aplică”, nu „zero”. Tratați absența lor ca normală, nu ca un bug în handler-ul dvs
Două motoare, o formă, o diferență discretă
HotXLS livrează două motoare în spatele acestui model identic de raportare, o fațadă BIFF8 pentru fișierele moștenite .xls și o fațadă OOXML pentru .xlsx, iar acestea nu expun IXLSWorkbookProgress identic. TXLSWorkbook, motorul .xls, implementează formal IXLSWorkbookProgress, așa că poate fi transmis oriunde este așteptat acel tip de interfață. TXLSXWorkbook, motorul .xlsx, expune aceiași membri Diagnostics, LastDiagnostic, OnDiagnostic, OnProgress și OnProgressEx cu nume și tipuri identice, dar ca o clasă simplă, nu ca o implementare formală a acelei interfețe, așa că nu va satisface un parametru IXLSWorkbookProgress de una singură. În practică asta contează rareori, pentru că majoritatea codului lucrează cu o clasă de registru de lucru concretă la un moment dat, dar înseamnă că nu puteți scrie un singur helper tipat pentru IXLSWorkbookProgress și să îi predați obiectul de registru de lucru al oricăruia din motoare interschimbabil. Singura diferență de câmp care rezultă direct din separarea de format este PartName: doar motorul XLSX îl populează, pentru că doar OOXML are părți zip de numit
Ce face un cod de diagnostic ceva pe care puteți ramifica în siguranță?
Câmpul Code este singura parte a unui diagnostic care merită să codificați hard o comparație față de ea; Message nu este, pentru că proza este exact genul de lucru care este reformulat, retradus, sau extins cu mai multe detalii într-o lansare ulterioară fără ca cineva să o trateze ca o schimbare care rupe compatibilitatea. Codurile de diagnostic încorporate în HotXLS deja se citesc ca și cum ar fi fost proiectate ținând cont de această distincție: codurile legate de salvare rulează de la 1000 la 1005, codurile legate de deschidere stau la 1100 și 1101, codurile legate de calcul la 1200 și 1201, și un cod de format neacceptat la 1300, cu goluri lăsate în interiorul fiecărei benzi, în loc ca acestea să ruleze consecutiv pe toate. Acea spațiere este ceea ce permite unui furnizor să adauge un nou mod de eșec la momentul salvării, să zicem, la 1006, fără a renumerota codurile de care depinde deja instrucțiunea dvs. switch, și merită verificat asta în orice API de diagnostice înainte de a vă angaja să potriviți pe un cod în producție, nu doar acesta. Păstrați o ramură implicită în propria logică de dispecerizare indiferent cât de stabilă pare numerotarea, pentru că noi moduri de eșec sunt exact ce continuă să descopere un parser sau un scriitor în evoluție. NativeCode și ExceptionClass stau un strat sub Code pentru când trebuie să escaladați: NativeCode păstrează valoarea de retur de bază, un HRESULT dintr-un apel Structured Storage printre ele, iar ExceptionClass înregistrează tipul de excepție Delphi când una a fost implicată, ceea ce este de obicei suficient pentru a deschide o cerere de suport precisă fără a atașa un stack trace complet
Severitatea și operația decid ce face codul dvs. în continuare
Severitatea și operația sunt cele care transformă un diagnostic dintr-o linie de log într-o decizie de rutare. TXLSDiagnosticSeverity rulează Info, Warning, Error și Fatal, iar TXLSDiagnosticOperation etichetează fiecare intrare cu apelul care a produs-o: Open, Save, Calculate sau Export. Cele două axe sunt independente prin design: xlsDiagnosticUnhandledException este un cod fix care se declanșează cu Operation setat la orice apel a ridicat-o efectiv, așa că Code răspunde la ce a mers prost, în timp ce Operation separat răspunde la unde, în loc să aibă nevoie de un cod distinct pentru o excepție în timpul deschiderii versus una în timpul salvării. Acea compoziționalitate este de asemenea ceea ce face rutarea mecanică: înregistrați un avertisment și continuați, o salvare anulată prin steagul Aborted este un exemplu tipic; numărați o eroare și continuați lotul, o foaie de calcul care nu a reușit să se serializeze este un exemplu tipic; opriți lotul la o severitate fatală, pentru că acel nivel înseamnă că o excepție netratată a derulat deja apelul, iar continuarea riscă să lucreze dintr-o stare pe jumătate actualizată. O precizare onestă: Info există în enumerare ca implicitul cu care pornește un TXLSDiagnostic proaspăt, dar fiecare punct de apel de diagnostic încorporat în lansarea actuală a HotXLS ridică vreodată doar Warning, Error sau Fatal; Info este rezervat pentru uz viitor, nu ceva ce motorul emite astăzi
// 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;
Conectarea OnDiagnostic într-o conductă de lot
Interogarea Diagnostics după fiecare apel funcționează pentru un singur fișier; încetează să funcționeze odată ce reveniți la acel lot de zece mii peste noapte, pentru că Diagnostics este golit la începutul fiecărui apel Open, SaveAs și Recalculate. Citiți-l după al treilea fișier dintr-o buclă și veți vedea doar diagnosticele celui de-al treilea fișier; orice au raportat primele două fișiere a dispărut deja. OnDiagnostic rezolvă asta transformând colecția într-un flux: abonați-vă o dată înainte ca bucla să înceapă, iar același handler se declanșează pentru fiecare fișier, în ordine, cu numele fișierului încă în domeniu prin intermediul unui câmp de instanță
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;
Cât costă efectiv callback-ul
OnDiagnostic este ieftin dintr-un motiv structural: se declanșează doar când ceva este deja greșit, iar greșit este rar comparativ cu numărul de celule, rânduri sau foi de calcul pe care le conține un registru de lucru. Contrastați asta cu OnProgress și OnProgressEx, care raportează progresul obișnuit și au trebuit proiectate în jurul frecvenței apelurilor încă de la început. HotXLS declanșează progresul la nivel de foaie o dată per foaie în timpul Open și SaveAs, nu o dată per celulă sau rând, ceea ce menține overhead-ul per apel mic chiar și pe registre de lucru cu milioane de celule; Recalculate merge mai departe și își limitează propriul eveniment de progres la aproximativ patru procente din graful de dependențe, astfel încât o recalculare completă vă oferă un puls în loc să vă inunde thread-ul UI cu evenimente. Diagnosticele nu au avut nevoie de nicio astfel de limitare, pentru că numărul de evenimente este limitat de numărul de probleme reale, nu de dimensiunea fișierului
Singurul loc unde performanța încă depinde de dvs. este în interiorul handler-ului însuși. OnDiagnostic se declanșează sincron, pe thread-ul care rulează Open, SaveAs sau Recalculate, așa că un handler care blochează, o scriere sincronă către un serviciu de logging la distanță de exemplu, devine parte din timpul de perete al acelui apel. Pentru un singur fișier asta este invizibil. Multiplicat pe un lot de zece mii de fișiere este diferența dintre un job care se termină peste noapte și unul care încă rulează la prânz, așa că puneți în buffer ce trebuie să facă handler-ul și eliberați-l asincron, în loc să faceți partea lentă inline
Diagnosticele structurate sunt cele mai valoroase exact acolo unde un rezultat boolean este cel mai slab, în fluxuri de lucru care ating multe fișiere în loc de unul singur. O conductă de audit și conversie a registrelor de lucru este cel mai clar exemplu: în loc să înregistreze un simplu trece/eșuează per fișier, atașați lista Diagnostics a fiecărui fișier la înregistrarea sa de audit, iar raportul vă spune nu doar ce a eșuat, ci de ce, ceea ce este majoritatea a ceea ce încearcă articolul nostru despre construirea unei bănci de lucru de audit și conversie a registrelor de lucru să facă bine în primul rând. Aceeași asociere de progres și diagnostice aparține de asemenea oricărui flux de lucru care are deja nevoie de raportare de progres pentru sine, ceea ce este exact teritoriul acoperit în ghidul nostru despre performanța registrelor de lucru mari în HotXLS, unde un apel lung Open sau SaveAs este suficient de comun încât OnProgress este deja conectat, iar OnDiagnostic este o adăugare naturală, aproape gratuită, alături de el
Nimic din toate acestea nu necesită Excel instalat undeva în conductă, și nimic din toate acestea nu necesită prinderea unei excepții generice și ghicirea a ce a însemnat. IXLSWorkbookProgress și membrii săi Diagnostics, LastDiagnostic și OnDiagnostic fac parte din componenta HotXLS standard pentru Delphi și C++Builder, alături de referința completă a codurilor de diagnostic și restul suprafeței Open, SaveAs și Recalculate prin care acest articol a trecut