Tekninen artikkeli

Rakenteellinen diagnostiikka totuusarvotulosten sijaan HotXLS:ssä

Aja eräkonversio kymmenentuhannelle laskentataulukolle yön yli, ja aamuun mennessä kolme niistä palautuu False-arvon. Se on koko jälkiselvitys, jonka totuusarvoinen tallennustulos antaa sinulle: määrä epäonnistumisia, ilman mitään siitä, mikä tiedosto, mikä arkki tai mikä kymmenestä mahdollisesta syystä oli vastuussa. HotXLS, losLabin natiivi Delphi- ja C++Builder-komponentti Excel-tiedostoille, korvaa tuon yhden bitin rakenteellisella diagnostiikalla. IXLSWorkbookProgress-rajapinta paljastaa Diagnostics-listan ja OnDiagnostic-tapahtuman, jotka raportoivat vakaan numeerisen koodin, vakavuustason, epäonnistuneen toiminnon ja arkin, jossa se tapahtui, jokaiselle Open-, SaveAs- ja Recalculate-kutsulle

Miksi totuusarvoinen tallennustulos epäonnistuu suuressa mittakaavassa?

Yksi epäonnistunut tiedosto ei ole ongelma, jonka totuusarvoinen tulos luo; tuhat niistä on. Kun SaveAs palauttaa jotain muuta kuin onnistumisen kolmelle tiedostolle kymmenestätuhannesta, seuraava kysymys on aina sama: ovatko nämä kolme uudelleenyritettäviä, vai tarvitsevatko ne ihmisen? Käyttöoikeusvirhe verkkojaossa ei ole sama tapaus kuin kaava, jota laskentamoottori ei pysty arvioimaan, eikä kumpikaan ole sama kuin työarkki, joka hiljaa ylitti muotorajan. Kun käytössä on vain läpäisy/epäonnistuminen-tulos, jokaisesta niistä tulee identtinen tukipyyntö, ja jonkun on avattava jokainen tiedosto käsin, Excelissä, ja tuijotettava sitä, kunnes syy käy ilmeiseksi. Tuo manuaalinen luokittelu on totuusarvoisen API:n todellinen kustannus, ja se skaalautuu lineaarisesti erän koon mukaan, mikä on juuri se ominaisuus, jota et halua virheenkäsittelyltä

IXLSWorkbookProgress:n sisällä: mitä TXLSDiagnostic kantaa

IXLSWorkbookProgress on rajapinta, jota HotXLS käyttää raportoidakseen sekä miten toiminto etenee että mikä sen sisällä meni pieleen, ja nämä kaksi puoliskoa jakavat yhden sopimuksen syystä: molemmat ovat asioita, jotka pitkäkestoisen Open-, SaveAs- tai Recalculate-kutsun on välitettävä nostamatta poikkeusta kesken toiminnon. Edistymispuoli on OnProgress ja OnProgressEx, jotka laukeavat vaiheen, tilan ja nykyinen/yhteensä-parin kanssa. Diagnostiikkapuoli on se, josta tämä artikkeli kertoo: Diagnostics-ominaisuus, joka palauttaa TXLSDiagnostics-listan, LastDiagnostic-oikotie viimeisimpään merkintään, ja OnDiagnostic-tapahtuma, joka laukeaa heti, kun jokainen TXLSDiagnostic-tietue luodaan. Jokainen tietue kantaa numeerisen Code-kentän, TXLSDiagnosticSeverity-vakavuuden, sen tuottaneen TXLSDiagnosticOperation-toiminnon, ihmisluettavan Message-viestin, SheetIndex- ja SheetName-kentät sekä NativeCode-kentän, joka säilyttää minkä tahansa matalamman tason paluuarvon, joka laukaisi merkinnän

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;

Diagnostics-listan lukeminen näin voittaa jo totuusarvoisen tuloksen yksinään, koska Code ja SheetName muuttavat mysteerin tarkaksi, suodatettavaksi tosiasiaksi. TXLSDiagnostic-tietue ulottuu pidemmälle kuin mitä tämä esimerkki tulostaa: RecordId ja StreamOffset ovat olemassa tavutason forensiikkaa varten BIFF-virran sisällä, ja PartName kantaa OOXML-zip-merkinnän, kuten xl/worksheets/sheet3.xml, josta ongelma tuli. Kannattaa tietää ennen kuin rakennat työkaluja niiden ympärille: nykyisessä julkaisussa mikään sisäänrakennettu diagnostiikkakutsupaikka ei täytä RecordId- tai StreamOffset-kenttiä, joten molemmat pysyvät konstruktorin oletusarvossa -1, mikä tarkoittaa "ei sovellettavissa" eikä "nolla". Kohtele niiden puuttumista normaalina, ei bugina käsittelijässäsi

Kaksi moottoria, yksi muoto, yksi hiljainen ero

HotXLS toimittaa kaksi moottoria tämän saman raportointimallin takana, BIFF8-julkisivun vanhoille .xls-tiedostoille ja OOXML-julkisivun .xlsx-tiedostoille, eivätkä ne paljasta IXLSWorkbookProgress-rajapintaa identtisesti. TXLSWorkbook, .xls-moottori, toteuttaa muodollisesti IXLSWorkbookProgress-rajapinnan, joten se voidaan välittää mihin tahansa, missä tuota rajapintatyyppiä odotetaan. TXLSXWorkbook, .xlsx-moottori, paljastaa samat Diagnostics-, LastDiagnostic-, OnDiagnostic-, OnProgress- ja OnProgressEx-jäsenet identtisillä nimillä ja tyypeillä, mutta tavallisena luokkana eikä tuon rajapinnan muodollisena toteutuksena, joten se ei sellaisenaan täytä IXLSWorkbookProgress-parametrin vaatimuksia. Käytännössä tällä on harvoin merkitystä, koska useimmat koodit työskentelevät yhtä konkreettista työkirjaluokkaa vasten kerrallaan, mutta se tarkoittaa, ettet voi kirjoittaa yhtä IXLSWorkbookProgress-tyyppistä apufunktiota ja antaa sille kummankin moottorin työkirjaoliota vaihdellen. Se yksi kenttäero, joka seuraa suoraan muotojaosta, on PartName: vain XLSX-moottori täyttää sen, koska vain OOXML:llä on zip-osia nimettäväksi

Mikä tekee diagnostiikkakoodista jotain, jonka perusteella voi turvallisesti haaroittaa?

Code-kenttä on ainoa osa diagnostiikkaa, jota kannattaa kovakoodata vertailussa; Message ei ole, koska proosa on juuri sitä, mikä sanoitetaan uudelleen, käännetään uudelleen tai laajennetaan lisädetaljilla myöhemmässä julkaisussa ilman että kukaan kohtelee sitä rikkovana muutoksena. HotXLS:n sisäänrakennetut diagnostiikkakoodit lukevat jo ikään kuin ne olisi suunniteltu tuo ero mielessä: tallennukseen liittyvät koodit kulkevat 1000–1005, avaamiseen liittyvät koodit sijaitsevat 1100:ssa ja 1101:ssä, laskentaan liittyvät koodit 1200:ssa ja 1201:ssä, ja tukematon-muoto-koodi 1300:ssa, aukkojen jäädessä jokaisen kaistan sisään sen sijaan, että koodit kulkisivat peräkkäin kaikkien yli. Tuo väli on se, mikä antaa toimittajalle mahdollisuuden lisätä uuden tallennusaikaisen vikatilan vaikkapa numeroon 1006 ilman, että se numeroi uudelleen koodit, joista switch-lauseesi jo riippuu, ja se kannattaa tarkistaa mistä tahansa diagnostiikka-API:sta ennen kuin sitoudut vertaamaan koodia tuotannossa, ei vain tästä yhdestä. Pidä oletushaara omassa jakelulogiikassasi riippumatta siitä, kuinka vakaalta numerointi näyttää, koska uudet vikatilat ovat juuri sitä, mitä kehittyvä jäsennin tai kirjoitin jatkuvasti löytää. NativeCode ja ExceptionClass istuvat yhden kerroksen Code-kentän alla, kun sinun on eskaloitava: NativeCode säilyttää taustalla olevan paluuarvon, joukossa niitä HRESULT Structured Storage -kutsusta, ja ExceptionClass tallentaa Delphi-poikkeustyypin, kun sellainen oli mukana, mikä on yleensä riittävä avaamaan tarkan tukipyynnön ilman koko pinojäljen liittämistä

Vakavuus ja toiminto päättävät, mitä koodisi tekee seuraavaksi

Vakavuus ja toiminto ovat se, mikä muuttaa diagnostiikan lokirivistä reititystäätökseksi. TXLSDiagnosticSeverity kulkee Info-, Warning-, Error- ja Fatal-tasojen läpi, ja TXLSDiagnosticOperation merkitsee jokaisen merkinnän sen tuottaneella kutsulla: Open, Save, Calculate tai Export. Nämä kaksi akselia ovat suunnittelultaan riippumattomia: xlsDiagnosticUnhandledException on yksi kiinteä koodi, joka laukeaa Operation-arvolla asetettuna mihin tahansa kutsuun, joka todella sen nosti, joten Code vastaa siihen, mikä meni pieleen, kun taas Operation vastaa erikseen siihen, missä, sen sijaan että tarvittaisiin erillinen koodi poikkeukselle avauksen aikana verrattuna tallennuksen aikaiseen. Tuo koostettavuus on myös se, mikä tekee reitityksestä mekaanista: kirjaa varoitus ja jatka, tallennus, joka peruutettiin Aborted-lipun kautta, on tyypillinen esimerkki; laske virhe ja pidä erä käynnissä, työarkki, joka ei onnistunut sarjallistumaan, on tyypillinen esimerkki; pysäytä erä fatal-vakavuudella, koska tuo taso tarkoittaa, että käsittelemätön poikkeus on jo purkanut kutsun, ja jatkaminen riskeeraa työskentelyn puolittain päivitetystä tilasta. Yksi rehellinen varoitus: Info on olemassa enumissa oletusarvona, jolla tuore TXLSDiagnostic alkaa, mutta jokainen tämän päivän HotXLS-julkaisuun sisäänrakennettu diagnostiikkakutsupaikka nostaa vain koskaan Warning-, Error- tai Fatal-tason; Info on varattu tulevaa käyttöä varten, ei jotain, mitä moottori tänään lähettää

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

OnDiagnostic-tapahtuman kytkeminen eräputkeen

Diagnostics-listan kyseleminen jokaisen kutsun jälkeen toimii yhdelle tiedostolle; se lakkaa toimimasta heti, kun palaat tuohon yön yli kestävään kymmenentuhannen tiedoston erään, koska Diagnostics tyhjennetään jokaisen Open-, SaveAs- ja Recalculate-kutsun alussa. Lue se kolmannen tiedoston jälkeen silmukassa, ja näet vain kolmannen tiedoston diagnostiikan; mitä tahansa ensimmäiset kaksi tiedostoa raportoivat, on jo poissa. OnDiagnostic ratkaisee tämän muuttamalla kokoelman virraksi: tilaa kerran ennen silmukan alkua, ja sama käsittelijä laukeaa jokaiselle tiedostolle, järjestyksessä, tiedostonimen pysyessä näkyvissä instanssikentän kautta

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;

Mitä takaisinkutsu todella maksaa

OnDiagnostic on halpa rakenteellisesta syystä: se laukeaa vain, kun jokin on jo pielessä, ja pieleen meneminen on harvinaista verrattuna työkirjan kantamien solujen, rivien tai työarkkien määrään. Vertaa tätä OnProgress- ja OnProgressEx-tapahtumiin, jotka raportoivat rutiininomaista edistymistä ja jotka oli suunniteltava kutsutaajuuden ympärille alusta asti. HotXLS laukaisee työarkkitason edistymisen kerran per arkki Open- ja SaveAs-kutsujen aikana, ei kerran per solu tai rivi, mikä on se, mikä pitää kutsukohtaisen ylikuorman pienenä jopa työkirjoissa, joissa on miljoonia soluja; Recalculate menee pidemmälle ja rajoittaa oman edistymistapahtumansa noin joka neljänteen prosenttiin riippuvuuskaaviosta, joten täysi uudelleenlaskenta antaa sinulle sykkeen sen sijaan, että tulvisi käyttöliittymäsäikeesi tapahtumilla. Diagnostiikka ei tarvinnut mitään tuosta rajoituksesta, koska tapahtumamäärä on rajattu todellisten ongelmien määrällä, ei tiedoston koolla

Ainoa paikka, jossa suorituskyky yhä riippuu sinusta, on itse käsittelijän sisällä. OnDiagnostic laukeaa synkronisesti, säikeessä, joka ajaa Open-, SaveAs- tai Recalculate-kutsua, joten käsittelijä, joka estää, esimerkiksi synkroninen kirjoitus etäloki­palveluun, muuttuu osaksi tuon kutsun seinäkelloaikaa. Yhdelle tiedostolle tämä on näkymätöntä. Kerrottuna kymmenentuhannen tiedoston erällä se on ero työn välillä, joka valmistuu yön yli, ja työn, joka on yhä käynnissä lounasaikaan, joten puskuroi se, mitä käsittelijän täytyy tehdä, ja tyhjennä se asynkronisesti sen sijaan, että tekisit hitaan osan rivissä

Rakenteellinen diagnostiikka on arvokkainta juuri siellä, missä totuusarvoinen tulos on heikoin, työnkuluissa, jotka koskettavat monta tiedostoa yhden sijaan. Työkirjan tarkastus- ja muunnosputki on selkein esimerkki: sen sijaan, että tallentaisit paljaan läpäisy/epäonnistuminen-tuloksen per tiedosto, liitä jokaisen tiedoston Diagnostics-lista sen tarkastustietueeseen, ja raportti kertoo sinulle ei vain, mikä epäonnistui, vaan miksi, mikä on suurin osa siitä, mitä artikkelimme työkirjan tarkastus- ja muunnostyöpenkin rakentamisesta yrittää saada oikein ensisijaisesti. Sama edistymisen ja diagnostiikan pariutus kuuluu myös mihin tahansa työnkulkuun, joka jo tarvitsee edistymisraportointia omista syistään, mikä on juuri se alue, joka käsitellään artikkelissamme HotXLS:n suurten työkirjojen suorituskykyoppaassa, jossa pitkä Open- tai SaveAs-kutsu on yleinen tarpeeksi, että OnProgress on jo kytketty ja OnDiagnostic on luonnollinen, lähes ilmainen lisäys sen viereen

Mikään tästä ei vaadi Exceliä asennettuna missään putken vaiheessa, eikä mikään tästä vaadi geneerisen poikkeuksen napata­mista ja arvaamista, mitä se tarkoitti. IXLSWorkbookProgress ja sen Diagnostics-, LastDiagnostic- ja OnDiagnostic-jäsenet ovat osa vakiomuotoista HotXLS-komponenttia Delphille ja C++Builderille, yhdessä täyden diagnostiikkakoodiviitteen ja tämän artikkelin läpikäymän Open-, SaveAs- ja Recalculate-pinnan muun osan kanssa