Esegui una conversione batch su diecimila fogli di calcolo durante la notte, e al mattino tre di essi tornano False. Questo è l'intero post-mortem che un risultato booleano di salvataggio ti offre: un conteggio di fallimenti, senza nulla su quale file, quale foglio, o quale delle dozzine di possibili cause fosse responsabile. HotXLS, il componente nativo di losLab per Delphi e C++Builder per i file Excel, sostituisce quel singolo bit con diagnostica strutturata. L'interfaccia IXLSWorkbookProgress espone un elenco Diagnostics e un evento OnDiagnostic che riportano un codice numerico stabile, un livello di severità, l'operazione che è fallita e il foglio in cui è accaduto, per ogni chiamata Open, SaveAs e Recalculate
Perché un risultato booleano di salvataggio fallisce su larga scala?
Un singolo file fallito non è il problema che crea un risultato booleano; mille di essi lo sono. Quando SaveAs restituisce qualcosa di diverso dal successo per tre file su diecimila, la domanda successiva è sempre la stessa: questi tre sono ritentabili, oppure serve un essere umano? Un errore di permessi su una condivisione di rete non è lo stesso incidente di una formula che il motore di calcolo non riesce a valutare, e nessuno dei due è uguale a un foglio di lavoro che ha silenziosamente superato un limite di formato. Con a disposizione solo un risultato pass/fail, ognuno di questi diventa un ticket di supporto identico, e qualcuno deve aprire ogni file a mano, in Excel, e fissarlo finché la causa non diventa ovvia. Quella triage manuale è il vero costo di un'API booleana, e scala linearmente con la dimensione del batch, che è esattamente la proprietà che non vuoi da una gestione degli errori
Dentro IXLSWorkbookProgress: cosa porta un TXLSDiagnostic
IXLSWorkbookProgress è l'interfaccia che HotXLS usa per riportare sia come sta procedendo un'operazione sia cosa è andato storto al suo interno, e le due metà condividono un unico contratto per un motivo: entrambe sono cose che una chiamata Open, SaveAs o Recalculate di lunga durata deve comunicare senza sollevare un'eccezione a metà operazione. La metà del progresso è OnProgress e OnProgressEx, che si generano con una fase, uno stato e una coppia corrente/totale. La metà della diagnostica è quella di cui tratta questo articolo: una proprietà Diagnostics che restituisce un elenco TXLSDiagnostics, una scorciatoia LastDiagnostic per la voce più recente, e un evento OnDiagnostic che si genera nel momento in cui ogni record TXLSDiagnostic viene creato. Ogni record porta un Code numerico, una TXLSDiagnosticSeverity, la TXLSDiagnosticOperation che l'ha prodotta, un Message leggibile per l'uomo, un SheetIndex e SheetName, e un NativeCode che preserva qualunque valore di ritorno di livello inferiore abbia scatenato la voce
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;
Leggere Diagnostics in questo modo batte già da solo un risultato booleano, perché Code e SheetName trasformano un mistero in un fatto specifico e filtrabile. Il record TXLSDiagnostic va oltre ciò che questo esempio stampa: RecordId e StreamOffset esistono per l'analisi forense a livello di byte dentro uno stream BIFF, e PartName contiene la voce zip OOXML, come xl/worksheets/sheet3.xml, da cui è provenuto un problema. Vale la pena sapere prima di costruire strumenti attorno a essi: nella release corrente nessuno dei punti di chiamata diagnostici integrati popola RecordId o StreamOffset, quindi entrambi restano al loro valore predefinito del costruttore di -1, che significa "non applicabile" piuttosto che "zero". Considera la loro assenza come normale, non come un bug nel tuo gestore
Due motori, una forma, una differenza silenziosa
HotXLS distribuisce due motori dietro questo stesso modello di segnalazione, una facciata BIFF8 per i file legacy .xls e una facciata OOXML per .xlsx, e non espongono IXLSWorkbookProgress in modo identico. TXLSWorkbook, il motore .xls, implementa formalmente IXLSWorkbookProgress, quindi può essere passato ovunque quel tipo di interfaccia sia atteso. TXLSXWorkbook, il motore .xlsx, espone gli stessi membri Diagnostics, LastDiagnostic, OnDiagnostic, OnProgress e OnProgressEx con nomi e tipi identici, ma come una semplice classe piuttosto che un'implementazione formale di quell'interfaccia, quindi da sola non soddisferà un parametro IXLSWorkbookProgress. In pratica questo raramente conta, perché la maggior parte del codice lavora contro una singola classe workbook concreta alla volta, ma significa che non puoi scrivere un unico helper tipizzato su IXLSWorkbookProgress e passargli l'oggetto workbook di uno dei due motori in modo intercambiabile. L'unica differenza di campo che segue direttamente dalla separazione dei formati è PartName: solo il motore XLSX lo popola, perché solo OOXML ha parti zip da nominare
Cosa rende un codice diagnostico qualcosa su cui puoi diramarti in sicurezza?
Il campo Code è l'unica parte di una diagnostica su cui vale la pena scrivere un confronto fisso nel codice; Message non lo è, perché il testo discorsivo è esattamente il tipo di cosa che viene riformulata, ritradotta o ampliata con più dettagli in una release successiva senza che nessuno lo consideri un cambiamento incompatibile. I codici diagnostici integrati di HotXLS già sembrano progettati tenendo a mente questa distinzione: i codici relativi al salvataggio vanno da 1000 a 1005, i codici relativi all'apertura si trovano a 1100 e 1101, i codici relativi al calcolo a 1200 e 1201, e un codice di formato non supportato a 1300, con vuoti lasciati dentro ciascuna banda invece che codici che scorrono consecutivamente attraverso tutte loro. Quella spaziatura è ciò che permette a un fornitore di aggiungere una nuova modalità di fallimento al momento del salvataggio a, diciamo, 1006 senza rinumerare i codici da cui il tuo statement switch già dipende, e vale la pena verificarla in qualsiasi API diagnostica prima di impegnarsi a fare corrispondenza su un codice in produzione, non solo in questa. Mantieni un ramo di default nella tua logica di dispatch indipendentemente da quanto stabile appaia la numerazione, perché nuove modalità di fallimento sono esattamente ciò che un parser o uno scrittore in evoluzione continua a scoprire. NativeCode ed ExceptionClass risiedono un livello sotto Code per quando devi fare escalation: NativeCode preserva il valore di ritorno sottostante, un HRESULT da una chiamata Structured Storage tra questi, ed ExceptionClass registra il tipo di eccezione Delphi quando ne era coinvolta una, il che di solito basta per aprire una richiesta di supporto precisa senza allegare uno stack trace completo
Severità e operazione decidono cosa fa dopo il tuo codice
Severità e operazione sono ciò che trasforma una diagnostica da una riga di log in una decisione di instradamento. TXLSDiagnosticSeverity comprende Info, Warning, Error e Fatal, e TXLSDiagnosticOperation etichetta ogni voce con la chiamata che l'ha prodotta: Open, Save, Calculate, o Export. I due assi sono indipendenti per progetto: xlsDiagnosticUnhandledException è un unico codice fisso che si genera con Operation impostata su qualunque chiamata l'abbia effettivamente sollevata, quindi Code risponde a cosa è andato storto mentre Operation separatamente risponde a dove, invece di richiedere un codice distinto per un'eccezione durante l'apertura rispetto a una durante il salvataggio. Questa componibilità è anche ciò che rende meccanico l'instradamento: registra un avviso e prosegui, un salvataggio annullato tramite il flag Aborted è un esempio tipico; conta un errore e mantieni il batch in esecuzione, un foglio di lavoro che non è riuscito a serializzarsi è un esempio tipico; ferma il batch a severità fatale, perché quel livello significa che un'eccezione non gestita ha già srotolato la chiamata e continuare rischia di lavorare da uno stato aggiornato solo a metà. Un avvertimento onesto: Info esiste nell'enumerazione come il valore predefinito con cui parte un TXLSDiagnostic appena creato, ma ogni punto di chiamata diagnostico integrato nella release odierna di HotXLS solleva sempre e solo Warning, Error o Fatal; Info è riservato per uso futuro, non qualcosa che il motore emette oggi
// 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;
Collegare OnDiagnostic in una pipeline batch
Effettuare il polling di Diagnostics dopo ogni chiamata funziona per un singolo file; smette di funzionare una volta tornati a quel batch notturno di diecimila, perché Diagnostics viene azzerato all'inizio di ogni chiamata Open, SaveAs e Recalculate. Leggilo dopo il terzo file in un ciclo e vedrai solo le diagnostiche del terzo file; qualunque cosa i primi due file abbiano segnalato è già scomparsa. OnDiagnostic risolve questo trasformando la collezione in uno stream: sottoscriviti una volta prima che il ciclo inizi, e lo stesso gestore si genera per ogni file, in ordine, con il nome del file ancora nell'ambito tramite un campo di istanza
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;
Cosa costa realmente il callback
OnDiagnostic è economico per una ragione strutturale: si genera solo quando qualcosa è già andato storto, e lo storto è raro rispetto al numero di celle, righe o fogli di lavoro che una cartella di lavoro contiene. Confrontalo con OnProgress e OnProgressEx, che segnalano il progresso di routine e hanno dovuto essere progettati attorno alla frequenza di chiamata fin dall'inizio. HotXLS genera il progresso a livello di foglio una volta per foglio durante Open e SaveAs, non una volta per cella o riga, il che è ciò che mantiene piccolo il sovraccarico per chiamata anche su cartelle di lavoro con milioni di celle; Recalculate va oltre e limita il proprio evento di progresso a circa ogni quattro percento del grafo delle dipendenze, cosicché un ricalcolo completo ti dia un battito cardiaco invece di inondare il tuo thread UI di eventi. La diagnostica non ha avuto bisogno di alcuna di quelle limitazioni, perché il conteggio degli eventi è limitato dal numero di problemi effettivi, non dalla dimensione del file
L'unico punto in cui le prestazioni dipendono ancora da te è dentro il gestore stesso. OnDiagnostic si genera in modo sincrono, sul thread che esegue Open, SaveAs o Recalculate, quindi un gestore che blocca, una scrittura sincrona verso un servizio di logging remoto ad esempio, diventa parte del tempo di orologio di quella chiamata. Per un singolo file è invisibile. Moltiplicato su un batch di diecimila file è la differenza tra un job che finisce durante la notte e uno che è ancora in esecuzione all'ora di pranzo, quindi accumula in buffer ciò che il gestore deve fare e scaricalo in modo asincrono invece di eseguire la parte lenta in linea
La diagnostica strutturata è più preziosa esattamente dove un risultato booleano è più debole, nei flussi di lavoro che toccano molti file invece di uno. Una pipeline di audit e conversione delle cartelle di lavoro è l'esempio più chiaro: invece di registrare un semplice pass/fail per file, allega l'elenco Diagnostics di ogni file al proprio record di audit, e il report ti dice non solo cosa è fallito ma perché, che è gran parte di ciò che il nostro articolo sulla costruzione di un banco di lavoro di audit e conversione delle cartelle di lavoro cerca di ottenere correttamente in primo luogo. Lo stesso abbinamento di progresso e diagnostica appartiene anche a qualsiasi flusso di lavoro che già necessita di segnalazione del progresso per suo conto, che è esattamente il territorio trattato nella nostra guida alle prestazioni delle cartelle di lavoro di grandi dimensioni in HotXLS, dove una lunga chiamata Open o SaveAs è abbastanza comune da avere già OnProgress collegato, e OnDiagnostic è un'aggiunta naturale e quasi gratuita accanto ad esso
Nulla di tutto ciò richiede Excel installato da qualche parte nella pipeline, e nulla di ciò richiede di catturare un'eccezione generica e indovinare cosa significasse. IXLSWorkbookProgress e i suoi membri Diagnostics, LastDiagnostic e OnDiagnostic fanno parte del componente HotXLS standard per Delphi e C++Builder, insieme al riferimento completo dei codici diagnostici e al resto della superficie Open, SaveAs e Recalculate che questo articolo ha percorso