Kjør en batch-konvertering over ti tusen regneark over natten, og innen morgenen kommer tre av dem tilbake False. Det er hele obduksjonsrapporten et boolsk lagringsresultat gir deg: et antall feil, uten noe om hvilken fil, hvilket ark, eller hvilken av et dusin mulige årsaker som var ansvarlig. HotXLS, losLabs native Delphi- og C++Builder-komponent for Excel-filer, erstatter den ene biten med strukturert diagnostikk. IXLSWorkbookProgress-grensesnittet eksponerer en Diagnostics-liste og en OnDiagnostic-hendelse som rapporterer en stabil numerisk kode, et alvorlighetsnivå, operasjonen som feilet, og arket der det skjedde, for hvert Open-, SaveAs-, og Recalculate-kall
Hvorfor svikter et boolsk lagringsresultat i stor skala?
Én mislykket fil er ikke problemet et boolsk resultat skaper; tusen av dem er det. Når SaveAs returnerer noe annet enn suksess for tre filer av ti tusen, er det neste spørsmålet alltid det samme: kan disse tre prøves på nytt, eller trenger de et menneske? En tillatelsesfeil på en nettverksdeling er ikke samme hendelse som en formel beregningsmotoren ikke kan evaluere, og ingen av dem er det samme som et regneark som stille overskred en formatgrense. Med bare et bestått/ikke-bestått-resultat å jobbe med, blir hver eneste av dem en identisk support-sak, og noen må åpne hver fil for hånd, i Excel, og stirre på den til årsaken blir åpenbar. Den manuelle triageringen er den reelle kostnaden av et boolsk API, og den skalerer lineært med batchens størrelse, noe som er nøyaktig den egenskapen man ikke vil ha fra feilhåndtering
Inne i IXLSWorkbookProgress: hva en TXLSDiagnostic bærer
IXLSWorkbookProgress er grensesnittet HotXLS bruker til å rapportere både hvordan en operasjon går og hva som gikk galt inne i den, og de to halvdelene deler én kontrakt av en grunn: begge er ting et langvarig Open-, SaveAs-, eller Recalculate-kall trenger å kommunisere uten å kaste et unntak midt i operasjonen. Fremdrifts-halvparten er OnProgress og OnProgressEx, som utløses med en fase, en tilstand, og et gjeldende/total-par. Diagnostikk-halvparten er den denne artikkelen handler om: en Diagnostics-egenskap som returnerer en TXLSDiagnostics-liste, en LastDiagnostic-snarvei for den nyeste oppføringen, og en OnDiagnostic-hendelse som utløses i det øyeblikket hver TXLSDiagnostic-record opprettes. Hver record bærer en numerisk Code, en TXLSDiagnosticSeverity, TXLSDiagnosticOperation-en som produserte den, en menneskelesbar Message, en SheetIndex og SheetName, og en NativeCode som bevarer hvilken som helst lavere-nivå-returverdi som utløste oppføringen
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;
Å lese Diagnostics slik som dette, slår allerede et boolsk resultat på egen hånd, fordi Code og SheetName gjør et mysterium om til et spesifikt, filtrerbart faktum. TXLSDiagnostic-recorden rekker lenger enn hva dette eksempelet skriver ut: RecordId og StreamOffset finnes for byte-nivå-rettsmedisin inne i en BIFF-strøm, og PartName holder OOXML-zip-oppføringen, slik som xl/worksheets/sheet3.xml, som et problem kom fra. Verdt å vite før man bygger verktøy rundt dem: i den gjeldende utgivelsen fyller ingen av de innebygde diagnostikk-kallstedene ut RecordId eller StreamOffset, så begge forblir på sin konstruktør-standard på -1, som betyr «ikke relevant» snarere enn «null». Behandle deres fravær som normalt, ikke som en bug i håndtereren din
To motorer, én form, én stille forskjell
HotXLS leverer to motorer bak denne samme rapporteringsmodellen, en BIFF8-fasade for eldre .xls-filer og en OOXML-fasade for .xlsx, og de eksponerer ikke IXLSWorkbookProgress identisk. TXLSWorkbook, .xls-motoren, implementerer IXLSWorkbookProgress formelt, slik at den kan overleveres hvor som helst det grensesnittet forventes. TXLSXWorkbook, .xlsx-motoren, eksponerer de samme Diagnostics-, LastDiagnostic-, OnDiagnostic-, OnProgress-, og OnProgressEx-medlemmene med identiske navn og typer, men som en ren klasse snarere enn en formell implementasjon av det grensesnittet, så den vil ikke tilfredsstille en IXLSWorkbookProgress-parameter på egen hånd. I praksis betyr dette sjelden noe, fordi det meste kode jobber mot én konkret arbeidsbok-klasse om gangen, men det betyr at man ikke kan skrive én enkelt hjelpefunksjon typet til IXLSWorkbookProgress og overlevere den enten motorens arbeidsbok-objekt om hverandre. Den ene feltforskjellen som følger direkte fra format-skillet, er PartName: bare XLSX-motoren fyller den ut, fordi bare OOXML har zip-deler å navngi
Hva gjør en diagnostikk-kode til noe man trygt kan forgrene på?
Code-feltet er den eneste delen av en diagnose verdt å hardkode en sammenligning mot; Message er det ikke, fordi prosa er nøyaktig den typen ting som blir omformulert, oversatt på nytt, eller utvidet med mer detalj i en senere utgivelse uten at noen behandler det som en brytende endring. HotXLS' innebygde diagnostikk-koder leser allerede ut som om de var designet med det skillet i tankene: lagringsrelaterte koder går fra 1000 til 1005, åpne-relaterte koder sitter på 1100 og 1101, beregnings-relaterte koder på 1200 og 1201, og en ustøttet-format-kode på 1300, med hull igjen inne i hvert bånd i stedet for at kodene løper fortløpende over alle sammen. Den avstanden er det som lar en leverandør legge til en ny lagringstidsfeilmodus på, si, 1006 uten å nummerere om kodene switch-setningen din allerede er avhengig av, og det er verdt å sjekke for i hvilket som helst diagnostikk-API før man forplikter seg til å matche på en kode i produksjon, ikke bare denne ene. Behold en standard-gren i din egen dispatch-logikk uansett hvor stabil nummereringen ser ut, fordi nye feilmodi er nøyaktig det en utviklende parser eller skriver stadig oppdager. NativeCode og ExceptionClass sitter ett lag under Code for når man trenger å eskalere: NativeCode bevarer den underliggende returverdien, en HRESULT fra et Structured Storage-kall blant dem, og ExceptionClass registrerer Delphi-unntakstypen når en var involvert, noe som vanligvis er nok til å åpne en presis support-forespørsel uten å legge ved en full stack trace
Alvorlighetsgrad og operasjon avgjør hva koden din gjør videre
Alvorlighetsgrad og operasjon er det som gjør en diagnose om fra en loglinje til en rutingsbeslutning. TXLSDiagnosticSeverity går gjennom Info, Warning, Error, og Fatal, og TXLSDiagnosticOperation tagger hver oppføring med kallet som produserte den: Open, Save, Calculate, eller Export. De to aksene er uavhengige med hensikt: xlsDiagnosticUnhandledException er én fast kode som utløses med Operation satt til hvilket kall som faktisk kastet den, slik at Code svarer på hva som gikk galt mens Operation separat svarer på hvor, i stedet for å trenge en distinkt kode for et unntak under åpning versus ett under lagring. Den komponerbarheten er også det som gjør ruting mekanisk: logg en advarsel og gå videre, en lagring avbrutt gjennom Aborted-flagget er et typisk eksempel; tell en feil og hold batchen kjørende, et regneark som feilet å serialisere er et typisk eksempel; stopp batchen ved en fatal alvorlighetsgrad, fordi det nivået betyr at et uhåndtert unntak allerede har viklet ut kallet, og å fortsette risikerer å jobbe fra en halvoppdatert tilstand. Én ærlig forbehold: Info finnes i enumen som standarden en fersk TXLSDiagnostic starter med, men hvert diagnostikk-kallsted bygget inn i dagens HotXLS-utgivelse kaster bare noensinne Warning, Error, eller Fatal; Info er reservert for fremtidig bruk, ikke noe motoren utsteder 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;
Å koble OnDiagnostic inn i en batch-pipeline
Å polle Diagnostics etter hvert kall fungerer for én enkelt fil; det slutter å fungere så snart man er tilbake til den natt-over-batchen på ti tusen, fordi Diagnostics tømmes ved starten av hvert Open-, SaveAs-, og Recalculate-kall. Les den etter den tredje filen i en løkke, og man ser bare den tredje filens diagnostikk; hva de to første filene rapporterte, er allerede borte. OnDiagnostic løser det ved å gjøre samlingen om til en strøm: abonner én gang før løkken starter, og den samme håndtereren utløses for hver fil, i rekkefølge, med filnavnet fortsatt i scope gjennom 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;
Hva callbacken faktisk koster
OnDiagnostic er billig av en strukturell grunn: den utløses bare når noe allerede er galt, og galt er sjeldent sammenlignet med antallet celler, rader, eller regneark en arbeidsbok holder. Kontrast det med OnProgress og OnProgressEx, som rapporterer rutinefremdrift og måtte designes rundt kallfrekvens fra starten av. HotXLS utløser regneark-nivå-fremdrift én gang per ark under Open og SaveAs, ikke én gang per celle eller rad, noe som er det som holder per-kall-overheaden liten selv på arbeidsbøker med millioner av celler; Recalculate går lenger og strupter sin egen fremdriftshendelse til omtrent hver fjerde prosent av avhengighetsgrafen, slik at en full omberegning gir deg et hjerteslag i stedet for å oversvømme UI-tråden med hendelser. Diagnostikk trengte ingen av den struptingen, fordi hendelsesantallet er begrenset av antallet faktiske problemer, ikke av filens størrelse
Det ene stedet ytelse fortsatt avhenger av deg, er inne i selve håndtereren. OnDiagnostic utløses synkront, på tråden som kjører Open, SaveAs, eller Recalculate, så en håndterer som blokkerer, en synkron skriving til en ekstern loggingstjeneste, for eksempel, blir en del av det kallets klokketid. For én enkelt fil er det usynlig. Multiplisert over en ti-tusen-fil-batch er det forskjellen mellom en jobb som fullfører over natten og én som fortsatt kjører til lunsj, så buffer det håndtereren trenger å gjøre og skyll det asynkront i stedet for å gjøre den trege delen inline
Strukturert diagnostikk er mest verdifull nøyaktig der et boolsk resultat er svakest, i arbeidsflyter som rører mange filer i stedet for én. En arbeidsbok-revisjons- og konverteringspipeline er det klareste eksempelet: i stedet for å registrere et bart bestått/ikke-bestått per fil, fest hver fils Diagnostics-liste til revisjonsposten, og rapporten forteller deg ikke bare hva som feilet, men hvorfor, noe som er mesteparten av det vår artikkel om å bygge en arbeidsbok-revisjons- og konverterings-arbeidsbenk prøver å få riktig i utgangspunktet. Den samme paringen av fremdrift og diagnostikk hører også hjemme i enhver arbeidsflyt som allerede trenger fremdriftsrapportering for sin egen del, noe som er nøyaktig territoriet dekket i vår guide til stor-arbeidsbok-ytelse i HotXLS, der et langt Open- eller SaveAs-kall er vanlig nok til at OnProgress allerede er koblet opp, og OnDiagnostic er et naturlig, nesten gratis tillegg ved siden av det
Ingenting av dette krever Excel installert noe sted i pipelinen, og ingenting av det krever å fange et generisk unntak og gjette hva det betydde. IXLSWorkbookProgress og medlemmene Diagnostics, LastDiagnostic, og OnDiagnostic er en del av den standard HotXLS-komponenten for Delphi og C++Builder, sammen med den fulle diagnostikk-kode-referansen og resten av Open-, SaveAs-, og Recalculate-overflaten denne artikkelen har gått gjennom