Pokrenete noćnu grupnu konverziju preko deset hiljada tabela, a ujutru se tri vrate sa vrednošću False. To je cela analiza problema koju vam Bulov rezultat čuvanja pruža: broj neuspeha, bez informacije o tome koja datoteka, koji list ili koji od desetak mogućih uzroka je odgovoran. HotXLS, izvorna losLab Delphi i C++Builder komponenta za Excel datoteke, zamenjuje taj jedan bit strukturisanom dijagnostikom. Interfejs IXLSWorkbookProgress izlaže listu Diagnostics i događaj OnDiagnostic koji za svaki poziv Open, SaveAs i Recalculate prijavljuju stabilan numerički kod, nivo ozbiljnosti, operaciju koja je neuspešna i list na kojem se problem dogodio
Zašto rezultat Bulovog čuvanja ne uspeva kada obim poraste?
Jedna neuspešna datoteka nije problem koji Bulov rezultat stvara; hiljadu takvih datoteka jeste. Kada SaveAs za tri od deset hiljada datoteka vrati bilo šta osim uspeha, sledeće pitanje je uvek isto: mogu li se te tri datoteke ponovo obraditi ili je potreban čovek? Greška dozvola na mrežnom deljenom resursu nije isti incident kao formula koju računski mehanizam ne može da izračuna, a nijedno od toga nije isto što i radni list koji je neprimetno prekoračio ograničenje formata. Kada imate samo rezultat prolaz/neuspeh, svaki od tih slučajeva postaje ista prijava podršci, pa neko mora ručno da otvori svaku datoteku u programu Excel i posmatra je dok uzrok ne postane očigledan. Ta ručna trijaža je stvarni trošak Bulovog API-ja i raste linearno sa veličinom grupe, što je upravo osobina koju ne želite kod obrade grešaka
Unutar IXLSWorkbookProgress: šta sadrži TXLSDiagnostic
IXLSWorkbookProgress je interfejs koji HotXLS koristi da prijavi i tok operacije i ono što je u njoj pošlo naopako, a te dve polovine dele jedan ugovor s razlogom: obe predstavljaju informacije koje dugotrajni poziv Open, SaveAs ili Recalculate mora da prenese bez podizanja izuzetka usred operacije. Deo za napredak čine OnProgress i OnProgressEx, koji se aktiviraju sa fazom, stanjem i parom trenutna vrednost/ukupna vrednost. Deo za dijagnostiku je tema ovog članka: svojstvo Diagnostics vraća listu TXLSDiagnostics, prečica LastDiagnostic daje najnoviji zapis, a događaj OnDiagnostic se aktivira čim se kreira svaki zapis TXLSDiagnostic. Svaki zapis sadrži numerički Code, TXLSDiagnosticSeverity, TXLSDiagnosticOperation koji ga je proizveo, čoveku čitljivu Message, SheetIndex i SheetName, kao i NativeCode koji čuva vrednost povratnog rezultata nižeg nivoa koja je pokrenula zapis
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;
Čitanje liste Diagnostics na ovaj način već samo po sebi nadmašuje Bulov rezultat, jer Code i SheetName pretvaraju misteriju u konkretnu činjenicu po kojoj se može filtrirati. Zapis TXLSDiagnostic ide dalje od onoga što ovaj primer ispisuje: RecordId i StreamOffset postoje za forenziku na nivou bajtova unutar BIFF toka, a PartName sadrži OOXML zip stavku, poput xl/worksheets/sheet3.xml, iz koje je problem potekao. Važno je znati pre nego što oko njih izgradite alate: u trenutnom izdanju nijedno ugrađeno mesto poziva dijagnostike ne popunjava RecordId ili StreamOffset, pa oba ostaju na podrazumevanoj vrednosti konstruktora -1, što znači „nije primenljivo“, a ne „nula“. Njihovo odsustvo tretirajte kao normalno ponašanje, a ne kao grešku u rukovaocu
Dva mehanizma, isti oblik, jedna tiha razlika
HotXLS isporučuje dva mehanizma iza ovog istog modela izveštavanja, BIFF8 fasadu za starije .xls datoteke i OOXML fasadu za .xlsx, ali oni ne izlažu IXLSWorkbookProgress na potpuno isti način. TXLSWorkbook, mehanizam za .xls, formalno implementira IXLSWorkbookProgress, pa se može proslediti svuda gde se očekuje taj tip interfejsa. TXLSXWorkbook, mehanizam za .xlsx, izlaže ista svojstva i događaje Diagnostics, LastDiagnostic, OnDiagnostic, OnProgress i OnProgressEx sa identičnim nazivima i tipovima, ali kao obična klasa, a ne kao formalna implementacija tog interfejsa, pa sam po sebi neće zadovoljiti parametar tipa IXLSWorkbookProgress. U praksi je to retko važno, jer većina koda istovremeno radi sa jednom konkretnom klasom radne sveske, ali znači da ne možete napisati pomoćnu funkciju tipiziranu kao IXLSWorkbookProgress i bez razlike joj proslediti objekat bilo kog mehanizma. Jedina razlika u polju koja direktno sledi iz razlike formata jeste PartName: popunjava ga samo XLSX mehanizam, jer samo OOXML ima zip delove kojima treba dodeliti naziv
Šta dijagnostički kod čini bezbednim za grananje?
Polje Code jedini je deo dijagnostike koji vredi hardkodovati u poređenju; Message nije, jer je proza upravo ono što se u kasnijem izdanju može preformulisati, ponovo prevesti ili proširiti dodatnim detaljima, a da se to ne smatra promenom koja narušava kompatibilnost. Ugrađeni HotXLS dijagnostički kodovi već deluju kao da su osmišljeni sa tom razlikom na umu: kodovi povezani sa čuvanjem obuhvataju 1000 do 1005, kodovi povezani sa otvaranjem su 1100 i 1101, oni za izračunavanje su 1200 i 1201, a kod za nepodržani format je 1300, uz ostavljene praznine unutar svakog opsega umesto uzastopnog numerisanja kroz sve opsege. Taj razmak omogućava proizvođaču da doda novi način neuspeha pri čuvanju, recimo 1006, bez renumerisanja kodova od kojih vaš switch već zavisi, a to vredi proveriti u svakom API-ju za dijagnostiku pre nego što se u produkciji oslonite na poređenje koda, ne samo u ovom. U sopstvenoj logici obrade uvek zadržite podrazumevanu granu, bez obzira na to koliko stabilno numerisanje izgleda, jer su novi načini neuspeha upravo ono što mehanizam za raščlanjivanje ili upis vremenom otkriva. NativeCode i ExceptionClass nalaze se jedan sloj ispod Code kada treba da prosledite problem podršci: NativeCode čuva osnovnu povratnu vrednost, uključujući HRESULT iz poziva Structured Storage, a ExceptionClass beleži tip Delphi izuzetka kada je bio prisutan, što je obično dovoljno za precizan zahtev podršci bez prilaganja kompletnog steka poziva
Ozbiljnost i operacija određuju sledeći potez vašeg koda
Ozbiljnost i operacija pretvaraju dijagnostiku iz reda dnevnika u odluku o usmeravanju. TXLSDiagnosticSeverity obuhvata Info, Warning, Error i Fatal, a TXLSDiagnosticOperation svakom zapisu pridružuje poziv koji ga je proizveo: Open, Save, Calculate ili Export. Te dve ose su namerno nezavisne: xlsDiagnosticUnhandledException je jedan nepromenljiv kod koji se aktivira sa vrednošću Operation postavljenom na poziv koji je zaista podigao izuzetak, pa Code odgovara šta je pošlo naopako, dok Operation zasebno odgovara gde se to dogodilo, umesto da je potreban poseban kod za izuzetak pri otvaranju i drugi pri čuvanju. Ta mogućnost kombinovanja čini usmeravanje mehaničkim: zabeležite upozorenje i nastavite, pri čemu je otkazivanje čuvanja preko zastavice Aborted tipičan primer; prebrojte grešku i nastavite grupu, pri čemu je radni list koji nije uspeo da se serijalizuje tipičan primer; zaustavite grupu kod fatalne ozbiljnosti, jer taj nivo znači da je neobrađeni izuzetak već prekinuo poziv i da nastavak može raditi nad poluizmenjenim stanjem. Jedna poštena napomena: Info postoji u enumeraciji kao podrazumevana vrednost sa kojom počinje novi TXLSDiagnostic, ali sva ugrađena mesta poziva dijagnostike u današnjem izdanju HotXLS-a podižu samo Warning, Error ili Fatal; Info je rezervisan za buduću upotrebu, a ne nešto što mehanizam danas emituje
// 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;
Povezivanje OnDiagnostic sa grupnim cevovodom
Čitanje liste Diagnostics nakon svakog poziva funkcioniše za jednu datoteku; prestaje da funkcioniše kada se vratite onoj noćnoj grupi od deset hiljada datoteka, jer se Diagnostics prazni na početku svakog poziva Open, SaveAs i Recalculate. Ako je pročitate posle treće datoteke u petlji, videćete samo dijagnostiku treće datoteke; sve što su prve dve prijavile već je nestalo. OnDiagnostic to rešava tako što kolekciju pretvara u tok: pretplatite se jednom pre početka petlje, a isti rukovalac se aktivira za svaku datoteku, redom, dok je naziv datoteke i dalje dostupan kroz polje instance
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;
Koliki je stvarni trošak povratnog poziva
OnDiagnostic je jeftin iz strukturnog razloga: aktivira se samo kada nešto već nije u redu, a problemi su retki u poređenju sa brojem ćelija, redova ili radnih listova u radnoj svesci. Uporedite to sa OnProgress i OnProgressEx, koji prijavljuju redovan napredak i od početka su morali biti projektovani prema učestalosti poziva. HotXLS aktivira napredak na nivou radnog lista jednom po listu tokom Open i SaveAs, a ne jednom po ćeliji ili redu, što održava mali dodatni trošak po pozivu čak i kod radnih sveski sa milionima ćelija; Recalculate ide korak dalje i sam ograničava učestalost događaja napretka na približno svaka četiri procenta grafa zavisnosti, pa vam potpuno izračunavanje daje otkucaj umesto da zatrpa nit korisničkog interfejsa događajima. Dijagnostici takvo ograničavanje nije potrebno, jer je broj događaja ograničen stvarnim brojem problema, a ne veličinom datoteke
Jedino mesto gde performanse i dalje zavise od vas jeste sam rukovalac. OnDiagnostic se aktivira sinhrono, na niti koja izvršava Open, SaveAs ili Recalculate, pa rukovalac koji blokira, na primer sinhrono upisivanje u udaljeni servis za evidentiranje, postaje deo ukupnog vremena tog poziva. Za jednu datoteku to je neprimetno. Kada se pomnoži sa grupom od deset hiljada datoteka, to je razlika između posla koji završi preko noći i posla koji još traje za vreme ručka, zato podatke koje rukovalac treba da obradi stavite u bafer i pošaljite ih asinhrono umesto da sporiji deo izvršavate direktno
Strukturisana dijagnostika je najvrednija upravo tamo gde je Bulov rezultat najslabiji, u tokovima rada koji dodiruju mnogo datoteka umesto jedne. Cevovod za proveru i konverziju radnih sveski najjasniji je primer: umesto da za svaku datoteku zabeležite samo prolaz/neuspeh, dodajte listu Diagnostics te datoteke njenom zapisu provere, pa izveštaj ne pokazuje samo šta nije uspelo već i zašto, što je najveći deo onoga što naš članak o izgradnji radnog okruženja za proveru i konverziju radnih sveski pokušava da postigne. Ista kombinacija napretka i dijagnostike pripada i svakom toku rada kojem je izveštavanje o napretku već potrebno samo po sebi, a to je upravo oblast obrađena u našem vodiču za performanse velikih radnih sveski u HotXLS-u, gde je dug poziv Open ili SaveAs dovoljno čest da je OnProgress već povezan, a OnDiagnostic predstavlja prirodan i gotovo besplatan dodatak pored njega
Ništa od ovoga ne zahteva da Excel bude instaliran bilo gde u cevovodu i ništa ne zahteva hvatanje generičkog izuzetka uz nagađanje njegovog značenja. IXLSWorkbookProgress i njegova svojstva i događaji Diagnostics, LastDiagnostic i OnDiagnostic deo su standardne HotXLS komponente za Delphi i C++Builder, zajedno sa potpunim referentnim pregledom dijagnostičkih kodova i ostatkom površine Open, SaveAs i Recalculate koju smo prošli u ovom članku