Če čez noč zaženete paketno pretvorbo deset tisoč preglednic, se zjutraj tri vrnejo z vrednostjo False. To je vsa analiza po koncu postopka, ki vam jo da logični rezultat shranjevanja: število napak, brez podatka o tem, katera datoteka, kateri delovni list ali kateri od ducata možnih vzrokov je bil odgovoren. HotXLS, izvorna komponenta losLab za Delphi in C++Builder za datoteke Excel, ta en sam bit nadomesti s strukturirano diagnostiko. Vmesnik IXLSWorkbookProgress izpostavlja seznam Diagnostics in dogodek OnDiagnostic, ki pri vsakem klicu Open, SaveAs in Recalculate sporočata stabilno številčno kodo, raven resnosti, operacijo, ki je odpovedala, in delovni list, kjer se je to zgodilo
Zakaj logični rezultat shranjevanja odpove pri velikem obsegu?
Problem, ki ga ustvari logični rezultat, ni ena sama neuspešna datoteka, temveč tisoč takih datotek. Ko SaveAs pri treh od deset tisoč datotek vrne nekaj drugega kot uspeh, je naslednje vprašanje vedno enako: ali je te tri mogoče ponovno obdelati ali potrebujejo človeka? Napaka dovoljenj na omrežni souporabi ni isti dogodek kot formula, ki je računski pogon ne more ovrednotiti, niti ni enaka delovnemu listu, ki je tiho presegel omejitev oblikovanja. Če imate na voljo le rezultat uspeh/neuspeh, se vsaka od teh napak spremeni v enak zahtevek za podporo, nekdo pa mora vsako datoteko ročno odpreti v Excelu in jo pregledovati, dokler vzrok ne postane očiten. To ročno razvrščanje je pravi strošek logičnega API-ja in narašča linearno z velikostjo paketa, kar je natanko lastnost, ki je pri obravnavi napak ne želite
V notranjosti IXLSWorkbookProgress: kaj vsebuje TXLSDiagnostic
IXLSWorkbookProgress je vmesnik, ki ga HotXLS uporablja za poročanje o poteku operacije in o tem, kaj je šlo v njej narobe, oba dela pa si isti kontrakt delita z razlogom: dolgotrajen klic Open, SaveAs ali Recalculate mora sporočiti oboje, ne da bi sredi operacije sprožil izjemo. Del za napredek sestavljata OnProgress in OnProgressEx, ki se sprožita s fazo, stanjem ter parom trenutne in skupne vrednosti. Diagnostični del je tema tega članka: lastnost Diagnostics vrne seznam TXLSDiagnostics, bližnjica LastDiagnostic vrne najnovejši vnos, dogodek OnDiagnostic pa se sproži v trenutku, ko je ustvarjen vsak zapis TXLSDiagnostic. Vsak zapis vsebuje številčno vrednost Code, TXLSDiagnosticSeverity, TXLSDiagnosticOperation, ki ga je ustvaril, človeku berljivo sporočilo Message, SheetIndex in SheetName ter NativeCode, ki ohrani katerokoli nižje-nivojsko vrnjeno vrednost, ki je sprožila vnos
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;
Že takšno branje Diagnostics je samo po sebi boljše od logičnega rezultata, saj Code in SheetName skrivnost spremenita v konkretno dejstvo, ki ga je mogoče filtrirati. Zapis TXLSDiagnostic sega dlje od tega, kar izpiše ta primer: RecordId in StreamOffset obstajata za forenziko na ravni bajtov znotraj toka BIFF, PartName pa vsebuje vnos OOXML zip, na primer xl/worksheets/sheet3.xml, iz katerega je izviral problem. Preden okoli njiju zgradite orodja, je dobro vedeti naslednje: v trenutni izdaji nobeno od vgrajenih diagnostičnih mest klica ne izpolni RecordId ali StreamOffset, zato oba ostaneta pri privzeti vrednosti konstruktorja -1, kar pomeni »ni uporabno«, ne pa »nič«. Njuna odsotnost je običajna in ne napaka v vašem obravnavalniku
Dva pogona, ena oblika, ena tiha razlika
HotXLS za tem istim modelom poročanja ponuja dva pogona, fasado BIFF8 za starejše datoteke .xls in fasado OOXML za .xlsx, ki pa vmesnika IXLSWorkbookProgress ne izpostavljata povsem enako. TXLSWorkbook, pogon za .xls, formalno implementira IXLSWorkbookProgress, zato ga je mogoče posredovati povsod, kjer se pričakuje ta vrsta vmesnika. TXLSXWorkbook, pogon za .xlsx, izpostavlja iste člane Diagnostics, LastDiagnostic, OnDiagnostic, OnProgress in OnProgressEx z enakimi imeni in tipi, vendar kot navaden razred in ne kot formalna implementacija tega vmesnika, zato sam po sebi ne bo ustrezal parametru IXLSWorkbookProgress. V praksi je to redko pomembno, saj večina kode hkrati dela z enim konkretnim razredom delovnega zvezka, pomeni pa, da ne morete napisati enega pomočnika s tipom IXLSWorkbookProgress in mu izmenično predajati objektov delovnega zvezka obeh pogonov. Edina razlika v polju, ki neposredno sledi iz ločitve formatov, je PartName: izpolni ga samo pogon XLSX, ker ima samo OOXML poimenovane zip dele
Kaj naredi diagnostično kodo varno za razvejanje?
Polje Code je edini del diagnostike, proti kateremu se je vredno neposredno primerjati; Message to ni, saj je proza natanko tista vrsta vsebine, ki jo je mogoče v poznejši izdaji preoblikovati, ponovno prevesti ali razširiti z več podrobnostmi, ne da bi to kdo obravnaval kot spremembo, ki lomi združljivost. Vgrajene diagnostične kode HotXLS so že videti, kot da so bile zasnovane z mislijo na to razliko: kode, povezane s shranjevanjem, segajo od 1000 do 1005, kode, povezane z odpiranjem, so 1100 in 1101, kode, povezane z izračunom, 1200 in 1201, koda za nepodprt format pa je 1300, z vrzelmi znotraj posameznega razpona namesto zaporednega štetja vseh kod. Prav ta razmik ponudniku omogoča, da doda nov način odpovedi pri shranjevanju, recimo 1006, ne da bi preštevilčil kode, od katerih je vaš stavek switch že odvisen, zato ga je vredno preveriti v kateremkoli API-ju za diagnostiko, preden se v produkciji odločite za ujemanje po kodi, ne le v tem API-ju. Ne glede na to, kako stabilno je videti oštevilčenje, v lastni logiki razpošiljanja ohranite privzeto vejo, saj novi načini odpovedi odkrivajo prav tisto, kar razvijajoči se razčlenjevalnik ali zapisovalnik še naprej odkriva. NativeCode in ExceptionClass sta eno plast pod Code, ko morate težavo stopnjevati: NativeCode ohrani osnovno vrnjeno vrednost, med drugim HRESULT iz klica Structured Storage, ExceptionClass pa zabeleži tip izjeme Delphi, kadar je bil ta vključen, kar je običajno dovolj za odprtje natančne zahteve za podporo brez pripenjanja celotnega sledenja sklada
Resnost in operacija določita naslednji korak vaše kode
Resnost in operacija spremenita diagnostiko iz vrstice dnevnika v odločitev o usmerjanju. TXLSDiagnosticSeverity vsebuje Info, Warning, Error in Fatal, TXLSDiagnosticOperation pa vsak vnos označi s klicem, ki ga je ustvaril: Open, Save, Calculate ali Export. Osi sta neodvisni po zasnovi: xlsDiagnosticUnhandledException je ena stalna koda, ki se sproži z Operation, nastavljenim na klic, ki je izjemo dejansko povzročil, zato Code odgovori, kaj je šlo narobe, Operation pa ločeno odgovori, kje, namesto da bi potrebovali posebno kodo za izjemo pri odpiranju in drugo pri shranjevanju. Prav ta sestavljivost omogoča mehansko usmerjanje: zabeležite opozorilo in nadaljujte, tipičen primer je shranjevanje, preklicano z zastavico Aborted; preštejte napako in pustite paket teči naprej, tipičen primer je delovni list, ki ga ni bilo mogoče serializirati; pri usodni resnosti paket ustavite, ker ta raven pomeni, da je neobravnavana izjema že prekinila klic, nadaljevanje pa tvega delo z napol posodobljenim stanjem. Ena poštena pripomba: Info obstaja v enumeraciji kot privzeta vrednost, s katero se začne nov TXLSDiagnostic, vendar vsa diagnostična mesta klica, vgrajena v današnjo izdajo HotXLS, sprožajo samo Warning, Error ali Fatal; Info je rezerviran za prihodnjo uporabo in ga pogon danes ne oddaja
// 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;
Vključitev OnDiagnostic v paketni cevovod
Preverjanje Diagnostics po vsakem klicu deluje za eno datoteko, preneha pa delovati, ko se vrnete k tistemu nočnemu paketu deset tisoč datotek, ker se Diagnostics počisti na začetku vsakega klica Open, SaveAs in Recalculate. Če ga v zanki preberete po tretji datoteki, vidite samo diagnostiko tretje datoteke; vse, kar sta sporočili prvi dve, je že izgubljeno. OnDiagnostic to reši tako, da zbirko spremeni v tok: naročite se enkrat pred začetkom zanke, isti obravnavalnik pa se sproži za vsako datoteko po vrstnem redu, pri čemer ime datoteke ostane dosegljivo prek polja primerka
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;
Kakšen strošek ima povratni klic
OnDiagnostic je poceni iz strukturnega razloga: sproži se samo, ko je nekaj že narobe, napake pa so redke v primerjavi s številom celic, vrstic ali delovnih listov v delovnem zvezku. Primerjajte to z dogodkoma OnProgress in OnProgressEx, ki poročata o običajnem napredku in sta morala biti že od začetka zasnovana okoli pogostosti klicev. HotXLS med Open in SaveAs poroča o napredku na ravni delovnega lista enkrat za vsak list, ne za vsako celico ali vrstico, zato ostane režija posameznega klica majhna tudi pri delovnih zvezkih z milijoni celic; Recalculate gre še dlje in lastni dogodek napredka omeji na približno vsake štiri odstotke grafa odvisnosti, tako da vam celoten ponovni izračun da utrip namesto poplave dogodkov v niti uporabniškega vmesnika. Diagnostika ni potrebovala nobenega takega omejevanja, ker je število dogodkov omejeno s številom dejanskih težav in ne z velikostjo datoteke
Edino mesto, kjer je zmogljivost še vedno odvisna od vas, je sam obravnavalnik. OnDiagnostic se sproži sinhrono v niti, ki izvaja Open, SaveAs ali Recalculate, zato obravnavalnik, ki blokira, na primer zaradi sinhronega zapisovanja v oddaljeno storitev za beleženje, postane del stenskega časa tega klica. Pri eni datoteki je to neopazno. Če to pomnožite z deset tisoč datotekami v paketu, dobite razliko med opravilom, ki se konča čez noč, in opravilom, ki še vedno teče ob kosilu, zato v obravnavalniku shranite podatke v medpomnilnik in jih izpraznite asinhrono, namesto da bi počasni del izvedli v vrstici klica
Strukturirana diagnostika je najdragocenejša natanko tam, kjer je logični rezultat najšibkejši, v potekih dela, ki obravnavajo veliko datotek namesto ene. Najjasnejši primer je cevovod za revizijo in pretvorbo delovnih zvezkov: namesto golega uspeha/neuspeha za vsako datoteko pripnite seznam Diagnostics vsake datoteke njenemu revizijskemu zapisu, poročilo pa vam pove ne le, kaj je odpovedalo, temveč tudi zakaj, kar je večina tega, kar želi doseči naš članek o izdelavi delovne postaje za revizijo in pretvorbo delovnih zvezkov. Ista povezava napredka in diagnostike sodi tudi v vsak potek dela, ki poročanje o napredku že potrebuje zaradi njega samega, kar je natanko področje, ki ga pokriva naš vodnik po zmogljivosti velikih delovnih zvezkov v HotXLS, kjer je dolg klic Open ali SaveAs dovolj pogost, da je OnProgress že povezan, OnDiagnostic pa je ob njem naraven in skoraj brezplačen dodatek
Nič od tega ne zahteva, da je Excel nameščen kjerkoli v cevovodu, prav tako ne zahteva prestrezanja splošne izjeme in ugibanja, kaj je pomenila. IXLSWorkbookProgress ter njegove lastnosti in dogodki Diagnostics, LastDiagnostic in OnDiagnostic so del standardne komponente HotXLS Component za Delphi in C++Builder, skupaj s celotnim sklicem diagnostičnih kod ter preostalim vmesnikom Open, SaveAs in Recalculate, ki ga je obravnaval ta članek