Tehnički članak

Dijagnostika umjesto logičkih rezultata u HotXLS-u

Pokrenete li noću serijsku konverziju deset tisuća proračunskih tablica, ujutro će se tri vratiti s rezultatom False. To je cijela analiza nakon događaja koju vam logički rezultat spremanja daje: broj neuspjeha, bez podatka o datoteci, listu ili jednom od desetak mogućih uzroka. HotXLS, nativna losLabova Delphi i C++Builder komponenta za Excelove datoteke, taj jedan bit zamjenjuje strukturiranom dijagnostikom. Sučelje IXLSWorkbookProgress izlaže popis Diagnostics i događaj OnDiagnostic koji za svaki poziv Open, SaveAs i Recalculate prijavljuju stabilan numerički kod, razinu ozbiljnosti, operaciju koja nije uspjela i list na kojem se problem dogodio

Zašto logički rezultat spremanja ne funkcionira u velikom opsegu

Jedna neuspješna datoteka nije problem koji stvara logički rezultat; problem ih je tisuću. Kada SaveAs za tri od deset tisuća datoteka vrati bilo što osim uspjeha, sljedeće je pitanje uvijek isto: mogu li se te tri datoteke ponovno pokušati obraditi ili trebaju čovjeka? Pogreška dozvole na mrežnom dijeljenju nije isti incident kao formula koju mehanizam za izračun ne može evaluirati, a ni jedno ni drugo nije isto što i radni list koji je neprimjetno premašio ograničenje formata. Ako imate samo rezultat prolaza ili neuspjeha, svaki od tih slučajeva postaje jednak zahtjev za podršku, a netko mora svaku datoteku ručno otvoriti u Excelu i promatrati je dok uzrok ne postane očit. Ta ručna trijaža stvarni je trošak logičkog API-ja i raste linearno s veličinom serije, upravo ono svojstvo koje ne želite od rukovanja pogreškama

Unutar IXLSWorkbookProgress: što sadrži TXLSDiagnostic

IXLSWorkbookProgress sučelje je koje HotXLS koristi za izvještavanje o tijeku operacije i onome što je u njoj pošlo po zlu, a dvije polovice dijele jedan ugovor s razlogom: dugotrajni poziv Open, SaveAs ili Recalculate mora moći priopćiti oboje bez podizanja iznimke usred operacije. Dio za napredak čine OnProgress i OnProgressEx, koji se aktiviraju s fazom, stanjem i parom trenutačne i ukupne vrijednosti. Dijagnostički dio tema je ovog članka: svojstvo Diagnostics vraća popis TXLSDiagnostics, LastDiagnostic daje prečac do najnovijeg unosa, a događaj OnDiagnostic aktivira se čim se stvori svaki zapis TXLSDiagnostic. Svaki zapis sadrži numerički Code, TXLSDiagnosticSeverity, TXLSDiagnosticOperation koji ga je proizveo, čovjeku čitljivu poruku Message, SheetIndex i SheetName te NativeCode koji čuva vrijednost povrata niže razine koja je pokrenula unos

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;

Već samo ovakvo čitanje popisa Diagnostics nadmašuje logički rezultat jer Code i SheetName nepoznanicu pretvaraju u konkretnu činjenicu po kojoj se može filtrirati. Zapis TXLSDiagnostic sadrži više od onoga što ovaj primjer ispisuje: RecordId i StreamOffset postoje za forenziku na razini bajtova unutar BIFF toka, a PartName sadrži OOXML zip unos, primjerice xl/worksheets/sheet3.xml, iz kojeg je problem potekao. Važno je znati prije izrade alata koji ovise o njima: u trenutačnom izdanju nijedno ugrađeno dijagnostičko mjesto poziva ne ispunjava RecordId ni StreamOffset, pa oba ostaju na zadanoj vrijednosti konstruktora -1, što znači "nije primjenjivo", a ne "nula". Njihovu odsutnost tretirajte kao normalnu pojavu, a ne kao pogrešku rukovatelja

Dva mehanizma, isti oblik, tiha razlika

HotXLS isporučuje dva mehanizma iza istog modela izvještavanja, BIFF8 sučelje za naslijeđene .xls datoteke i OOXML sučelje za .xlsx, a ona ne izlažu IXLSWorkbookProgress potpuno jednako. TXLSWorkbook, mehanizam za .xls, formalno implementira IXLSWorkbookProgress, pa se može proslijediti svugdje gdje se očekuje taj tip sučelja. TXLSXWorkbook, mehanizam za .xlsx, izlaže ista svojstva Diagnostics, LastDiagnostic, OnDiagnostic, OnProgress i OnProgressEx s jednakim imenima i tipovima, ali kao obična klasa, a ne kao formalna implementacija tog sučelja, pa sam po sebi neće zadovoljiti parametar tipa IXLSWorkbookProgress. U praksi je to rijetko važno jer većina koda istodobno radi s jednom konkretnom klasom radne knjige, ali znači da ne možete napisati jedan pomoćni postupak tipiziran kao IXLSWorkbookProgress i neizmjenično mu predati objekte radnih knjiga oba mehanizma. Jedina razlika u poljima koja izravno proizlazi iz podjele formata jest PartName: ispunjava ga samo XLSX mehanizam jer samo OOXML ima zip dijelove koje treba imenovati

Što dijagnostički kod čini sigurnim za grananje

Polje Code jedini je dio dijagnostike prema kojem vrijedi tvrdo uspoređivati; Message nije, jer se tekst može preformulirati, ponovno prevesti ili proširiti s više pojedinosti u kasnijem izdanju, a da se to ne smatra promjenom koja narušava kompatibilnost. Ugrađeni dijagnostički kodovi HotXLS-a već djeluju kao da su osmišljeni s tom razlikom na umu: kodovi povezani sa spremanjem protežu se od 1000 do 1005, oni povezani s otvaranjem nalaze se na 1100 i 1101, kodovi izračuna na 1200 i 1201, a kod nepodržanog formata na 1300, uz ostavljene praznine unutar svake skupine umjesto uzastopnog brojanja kroz sve kodove. Taj razmak dobavljaču omogućuje dodavanje novog načina neuspjeha pri spremanju, primjerice 1006, bez renumeriranja kodova o kojima ovisi vaša postojeća naredba grananja, a to vrijedi provjeriti u svakom API-ju dijagnostike prije nego što u produkciji počnete uspoređivati kodove, ne samo u ovom. U vlastitoj logici usmjeravanja uvijek zadržite zadanu granu, bez obzira na to koliko stabilno izgleda numeriranje, jer nove načine neuspjeha upravo stalno otkrivaju parser ili zapisivač koji se razvijaju. NativeCode i ExceptionClass nalaze se jedan sloj ispod Code-a kada treba eskalirati problem: NativeCode čuva temeljnu vrijednost povrata, među ostalim HRESULT iz poziva Structured Storage, a ExceptionClass bilježi tip Delphi iznimke kada je bila uključena, što je obično dovoljno za otvaranje preciznog zahtjeva podršci bez prilaganja cijelog traga stoga

Ozbiljnost i operacija određuju sljedeću radnju koda

Ozbiljnost i operacija pretvaraju dijagnostiku iz retka zapisnika u odluku o usmjeravanju. TXLSDiagnosticSeverity sadrži Info, Warning, Error i Fatal, a TXLSDiagnosticOperation svaki unos označava pozivom koji ga je proizveo: Open, Save, Calculate ili Export. Dvije su osi namjerno neovisne: xlsDiagnosticUnhandledException jedan je fiksni kod koji se aktivira uz Operation postavljen na poziv koji je doista podigao iznimku, pa Code odgovara što je pošlo po zlu, dok Operation zasebno odgovara gdje se to dogodilo, bez potrebe za posebnim kodom za iznimku pri otvaranju i drugim kodom za iznimku pri spremanju. Ta kombinacija također čini usmjeravanje mehaničkim: zabilježite upozorenje i nastavite, što je tipičan slučaj za spremanje otkazano kroz oznaku Aborted; prebrojite pogrešku i nastavite serijsku obradu, što je tipično za radni list koji se nije mogao serijalizirati; zaustavite seriju kod ozbiljnosti fatal, jer ta razina znači da je neobrađena iznimka već prekinula poziv, a nastavak rada nosi rizik korištenja napola ažuriranog stanja. Jedna poštena napomena: Info postoji u enumeraciji kao zadana vrijednost s kojom počinje novi TXLSDiagnostic, ali sva ugrađena dijagnostička mjesta poziva u današnjem izdanju HotXLS-a podižu samo Warning, Error ili Fatal; Info je rezerviran za buduću uporabu, a ne za ono što mehanizam danas emitira

// 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;

Uključivanje OnDiagnostic u serijski postupak

Anketiranje popisa Diagnostics nakon svakog poziva funkcionira za jednu datoteku; prestaje funkcionirati kada se vratite na noćnu seriju od deset tisuća datoteka jer se Diagnostics prazni na početku svakog poziva Open, SaveAs i Recalculate. Ako ga u petlji pročitate nakon treće datoteke, vidjet ćete samo dijagnostiku treće datoteke; sve što su prijavile prve dvije već je nestalo. OnDiagnostic to rješava pretvaranjem zbirke u tok: pretplatite se jednom prije početka petlje, a isti se rukovatelj 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 je nešto već pogrešno, a pogreške su rijetke u usporedbi s brojem ćelija, redaka ili radnih listova koje radna knjiga sadrži. Usporedite ga s OnProgress i OnProgressEx, koji prijavljuju uobičajeni napredak i od početka su morali biti projektirani prema učestalosti poziva. HotXLS tijekom Open i SaveAs napredak na razini radnog lista aktivira jednom po listu, a ne jednom po ćeliji ili retku, čime se održava malo opterećenje svakog poziva čak i na radnim knjigama s milijunima ćelija; Recalculate ide korak dalje i vlastiti događaj napretka ograničava na približno svaka četiri posto grafa ovisnosti, pa potpuni izračun daje otkucaj umjesto preplavljivanja niti korisničkog sučelja događajima. Dijagnostici takvo ograničavanje nije potrebno jer je broj događaja ograničen stvarnim brojem problema, a ne veličinom datoteke

Jedino mjesto na kojem performanse i dalje ovise o vama jest sam rukovatelj. OnDiagnostic aktivira se sinkrono, na niti koja izvršava Open, SaveAs ili Recalculate, pa rukovatelj koji blokira, primjerice sinkronim zapisom u udaljenu uslugu zapisivanja, postaje dio proteklog vremena tog poziva. Za jednu je datoteku to neprimjetno. Kada se pomnoži sa serijom od deset tisuća datoteka, razlika je između posla koji završi preko noći i onoga koji još traje za vrijeme ručka, zato spremite ono što rukovatelj mora obaviti u međuspremnik i ispišite to asinkrono umjesto da sporiji dio izvršavate u istoj niti

Strukturirana dijagnostika najvrjednija je upravo tamo gdje je logički rezultat najslabiji, u tijekovima rada koji obrađuju mnogo datoteka umjesto jedne. Najjasniji je primjer cjevovod revizije i konverzije radnih knjiga: umjesto bilježenja golog prolaza ili neuspjeha po datoteci, popis Diagnostics svake datoteke pridružite njezinu revizijskom zapisu, pa izvještaj ne govori samo što je neuspjelo nego i zašto, što je i glavni cilj našeg članka o izradi radnog stola za reviziju i konverziju radnih knjiga. Ista kombinacija napretka i dijagnostike pripada svakom tijeku rada koji već treba izvještavanje o napretku, a upravo je to područje obuhvaćeno u našem vodiču za performanse velikih radnih knjiga u HotXLS-u, gdje je dug poziv Open ili SaveAs dovoljno čest da je OnProgress već povezan, a OnDiagnostic prirodan i gotovo besplatan dodatak uz njega

Za sve to nije potreban instaliran Excel ni u jednom dijelu cjevovoda, niti je potrebno uhvatiti generičku iznimku i nagađati što znači. IXLSWorkbookProgress i njegova svojstva Diagnostics, LastDiagnostic i OnDiagnostic dio su standardne komponente HotXLS za Delphi i C++Builder, zajedno s potpunim referentnim popisom dijagnostičkih kodova i ostatkom površine Open, SaveAs i Recalculate kroz koju je ovaj članak prošao