Kør en batch-konvertering over ti tusind regneark natten over, og om morgenen kommer tre af dem tilbage False. Det er hele obduktionen, et boolsk gemme-resultat giver dig: en optælling af fejl, uden noget om hvilken fil, hvilket ark, eller hvilken af et dusin mulige årsager der var ansvarlig. HotXLS, losLabs native Delphi- og C++Builder-komponent til Excel-filer, erstatter den ene bit med strukturerede diagnostikker. IXLSWorkbookProgress-grænsefladen eksponerer en Diagnostics-liste og en OnDiagnostic-hændelse, der rapporterer en stabil numerisk kode, et alvorlighedsniveau, den operation der fejlede, og det ark hvor det skete, for hvert Open-, SaveAs- og Recalculate-kald
Hvorfor fejler et boolsk gemme-resultat i stor skala?
En enkelt mislykket fil er ikke det problem, et boolsk resultat skaber; et tusind af dem er. Når SaveAs returnerer noget andet end succes for tre filer ud af ti tusind, er det næste spørgsmål altid det samme: er disse tre genforsøgbare, eller har de brug for et menneske? En tilladelsesfejl på en netværksdeling er ikke den samme hændelse som en formel, beregningsmotoren ikke kan evaluere, og ingen af dem er den samme som et regneark, der i stilhed overskred en formatgrænse. Med kun et bestået/ikke-bestået-resultat at arbejde med bliver hver eneste af dem en identisk supportsag, og nogen må åbne hver fil i hånden, i Excel, og stirre på den, indtil årsagen bliver indlysende. Den manuelle triage er den reelle omkostning ved en boolsk API, og den skalerer lineært med batchens størrelse, hvilket er præcis den egenskab, man ikke ønsker fra fejlhåndtering
Inde i IXLSWorkbookProgress: hvad en TXLSDiagnostic bærer
IXLSWorkbookProgress er grænsefladen HotXLS bruger til at rapportere både, hvordan en operation forløber, og hvad der gik galt inden i den, og de to halvdele deler én kontrakt af en grund: begge er ting et langvarigt Open-, SaveAs- eller Recalculate-kald har brug for at kommunikere uden at kaste en undtagelse midt i operationen. Fremskridts-halvdelen er OnProgress og OnProgressEx, der udløses med en fase, en tilstand og et aktuel/total-par. Diagnostik-halvdelen er den, denne artikel handler om: en Diagnostics-egenskab der returnerer en TXLSDiagnostics-liste, en LastDiagnostic-genvej til den seneste post, og en OnDiagnostic-hændelse der udløses, i det øjeblik hver TXLSDiagnostic-record oprettes. Hver post bærer en numerisk Code, en TXLSDiagnosticSeverity, den TXLSDiagnosticOperation der producerede den, en menneskelæsbar Message, et SheetIndex og SheetName, og en NativeCode der bevarer hvilken som helst lavere-niveau-returværdi, der udløste posten
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;
At læse Diagnostics på denne måde slår allerede et boolsk resultat på egen hånd, fordi Code og SheetName gør et mysterium til en specifik, filtrerbar kendsgerning. TXLSDiagnostic-recorden rækker længere end det, dette eksempel udskriver: RecordId og StreamOffset findes til byte-niveau-retsmedicin inde i en BIFF-stream, og PartName holder OOXML-zip-posten, såsom xl/worksheets/sheet3.xml, et problem kom fra. Værd at kende, før man bygger værktøjer omkring dem: i den aktuelle udgivelse fylder ingen af de indbyggede diagnostik-kaldesteder RecordId eller StreamOffset, så begge forbliver på deres konstruktør-standard på -1, hvilket betyder "ikke relevant" frem for "nul". Behandl deres fravær som normalt, ikke som en fejl i din handler
To motorer, én form, én stille forskel
HotXLS leverer to motorer bag denne samme rapporteringsmodel, en BIFF8-facade til legacy-.xls-filer og en OOXML-facade til .xlsx, og de eksponerer ikke IXLSWorkbookProgress identisk. TXLSWorkbook, .xls-motoren, implementerer formelt IXLSWorkbookProgress, så den kan overdrages hvor som helst den grænsefladetype forventes. TXLSXWorkbook, .xlsx-motoren, eksponerer de samme Diagnostics-, LastDiagnostic-, OnDiagnostic-, OnProgress- og OnProgressEx-medlemmer med identiske navne og typer, men som en almindelig klasse frem for en formel implementering af den grænseflade, så den vil ikke tilfredsstille en IXLSWorkbookProgress-parameter på egen hånd. I praksis betyder dette sjældent noget, fordi det meste kode arbejder mod én konkret projektmappe-klasse ad gangen, men det betyder, at man ikke kan skrive én enkelt hjælper typet til IXLSWorkbookProgress og overdrage den enten motors projektmappe-objekt om hverandre. Den ene feltforskel, der følger direkte af formatopdelingen, er PartName: kun XLSX-motoren fylder den, fordi kun OOXML har zip-dele at navngive
Hvad gør en diagnostikkode til noget, man sikkert kan forgrene på?
Code-feltet er den eneste del af en diagnostik, det er værd at hardkode en sammenligning mod; Message er det ikke, fordi prosa er præcis den slags, der omformuleres, genoversættes eller udvides med mere detalje i en senere udgivelse, uden at nogen behandler det som en brydende ændring. HotXLSs indbyggede diagnostikkoder læser allerede, som om de var designet med det skel i tankerne: gemme-relaterede koder kører 1000 til 1005, åbne-relaterede koder sidder ved 1100 og 1101, beregne-relaterede koder ved 1200 og 1201, og en ikke-understøttet-format-kode ved 1300, med huller efterladt inde i hvert bånd frem for at koderne kører fortløbende på tværs af dem alle. Den afstand er, hvad der lader en leverandør tilføje en ny gemme-tids-fejltilstand ved, sig, 1006 uden at omnummerere de koder, ens switch-sætning allerede er afhængig af, og det er værd at tjekke for i enhver diagnostik-API, før man forpligter sig til at matche på en kode i produktion, ikke kun denne ene. Behold en standard-gren i ens egen dispatch-logik uanset, hvor stabil nummereringen ser ud, fordi nye fejltilstande er præcis, hvad en udviklende parser eller skriver bliver ved med at opdage. NativeCode og ExceptionClass sidder ét lag under Code, når man har brug for at eskalere: NativeCode bevarer den underliggende returværdi, en HRESULT fra et Structured Storage-kald blandt dem, og ExceptionClass registrerer Delphi-undtagelsestypen, når en var involveret, hvilket sædvanligvis er nok til at åbne en præcis supportanmodning uden at vedhæfte et fuldt stack trace
Alvorlighed og operation afgør, hvad ens kode gør derefter
Alvorlighed og operation er det, der gør en diagnostik fra en log-linje til en routing-beslutning. TXLSDiagnosticSeverity kører Info, Warning, Error og Fatal, og TXLSDiagnosticOperation mærker hver post med det kald, der producerede den: Open, Save, Calculate eller Export. De to akser er uafhængige af design: xlsDiagnosticUnhandledException er én fast kode, der udløses med Operation sat til hvilket kald der end rent faktisk kastede den, så Code svarer på hvad der gik galt, mens Operation separat svarer på hvor, i stedet for at have brug for en distinkt kode til en undtagelse under åbning versus en under gemning. Den sammensætbarhed er også, hvad der gør routing mekanisk: log en advarsel og fortsæt, en gemning annulleret gennem Aborted-flaget er et typisk eksempel; tæl en fejl og hold batchen kørende, et regneark der ikke lykkedes at serialisere er et typisk eksempel; stop batchen ved en fatal alvorlighed, fordi det niveau betyder, at en uhåndteret undtagelse allerede har rullet kaldet tilbage, og at fortsætte risikerer at arbejde ud fra en halvopdateret tilstand. Ét ærligt forbehold: Info findes i enum'en som standarden, en frisk TXLSDiagnostic starter med, men hvert diagnostik-kaldested indbygget i dagens HotXLS-udgivelse rejser kun nogensinde Warning, Error eller Fatal; Info er reserveret til fremtidig brug, ikke noget motoren udsender i dag
// 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;
At koble OnDiagnostic ind i en batch-pipeline
At polle Diagnostics efter hvert kald fungerer for én enkelt fil; det holder op med at fungere, når man er tilbage til den overnattende batch på ti tusind, fordi Diagnostics ryddes ved starten af hvert Open-, SaveAs- og Recalculate-kald. Læs den efter den tredje fil i en løkke, og man ser kun den tredje fils diagnostikker; hvad end de første to filer rapporterede, er allerede væk. OnDiagnostic løser det ved at gøre samlingen til en strøm: abonner én gang, før løkken starter, og den samme handler udløses for hver fil, i rækkefølge, med filnavnet stadig inden for scope gennem et instansfelt
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;
Hvad callback'et rent faktisk koster
OnDiagnostic er billigt af en strukturel grund: det udløses kun, når noget allerede er galt, og galt er sjældent sammenlignet med antallet af celler, rækker eller regneark, en projektmappe indeholder. Sammenlign det med OnProgress og OnProgressEx, som rapporterer rutinemæssigt fremskridt og skulle designes omkring kaldfrekvens fra begyndelsen. HotXLS udløser regneark-niveau-fremskridt én gang pr. ark under Open og SaveAs, ikke én gang pr. celle eller række, hvilket er det, der holder overheaden pr. kald lille selv på projektmapper med millioner af celler; Recalculate går videre og strupper sin egen fremskridts-hændelse til omtrent hver fjerde procent af afhængighedsgrafen, så en fuld genberegning giver dig et hjerteslag i stedet for at oversvømme din UI-tråd med hændelser. Diagnostikker havde brug for ingen af den strupning, fordi hændelsesantallet er begrænset af antallet af faktiske problemer, ikke af filens størrelse
Det ene sted, ydeevne stadig afhænger af dig, er inde i selve handleren. OnDiagnostic udløses synkront, på den tråd der kører Open, SaveAs eller Recalculate, så en handler der blokerer, en synkron skrivning til en fjern-logtjeneste for eksempel, bliver en del af det kalds vægtur-tid. For en enkelt fil er det usynligt. Ganget over en ti-tusind-fil-batch er det forskellen mellem et job, der afsluttes natten over, og et der stadig kører til frokost, så buffer hvad handleren har brug for at gøre, og flush det asynkront frem for at gøre den langsomme del inline
Strukturerede diagnostikker er mest værdifulde netop der, hvor et boolsk resultat er svagest, i workflows der rører mange filer i stedet for én. En projektmappe-audit- og konverteringspipeline er det klareste eksempel: i stedet for at registrere et rent bestået/ikke-bestået pr. fil, vedhæft hver fils Diagnostics-liste til dens audit-post, og rapporten fortæller dig ikke bare hvad der fejlede, men hvorfor, hvilket er det meste af, hvad vores artikel om at bygge en projektmappe-audit- og konverterings-workbench forsøger at få rigtigt i første omgang. Den samme parring af fremskridt og diagnostikker hører også hjemme i ethvert workflow, der allerede har brug for fremskridtsrapportering for sin egen skyld, hvilket er præcis det territorium dækket i vores guide til ydeevne ved store projektmapper i HotXLS, hvor et langt Open- eller SaveAs-kald er almindeligt nok til, at OnProgress allerede er koblet til, og OnDiagnostic er en naturlig, næsten gratis tilføjelse ved siden af det
Intet af dette kræver Excel installeret nogen steder i pipelinen, og intet af det kræver at fange en generisk undtagelse og gætte, hvad den betød. IXLSWorkbookProgress og dens Diagnostics-, LastDiagnostic- og OnDiagnostic-medlemmer er en del af standard-HotXLS-komponenten til Delphi og C++Builder, sammen med den fulde diagnostikkode-reference og resten af Open-, SaveAs- og Recalculate-fladen denne artikel har gennemgået