Εκτελέστε μια μαζική μετατροπή σε δέκα χιλιάδες υπολογιστικά φύλλα κατά τη διάρκεια της νύχτας, και μέχρι το πρωί τρία από αυτά επιστρέφουν False. Αυτή είναι ολόκληρη η μεταθανάτια ανάλυση που σας δίνει ένα boolean αποτέλεσμα αποθήκευσης: μια μέτρηση αποτυχιών, χωρίς τίποτα σχετικά με ποιο αρχείο, ποιο φύλλο, ή ποια από μια ντουζίνα πιθανές αιτίες ήταν υπεύθυνη. Το HotXLS, το γηγενές εξάρτημα της losLab για Delphi και C++Builder για αρχεία Excel, αντικαθιστά εκείνο το μοναδικό bit με δομημένη διαγνωστική πληροφορία. Η διεπαφή IXLSWorkbookProgress εκθέτει μια λίστα Diagnostics και ένα συμβάν OnDiagnostic που αναφέρουν έναν σταθερό αριθμητικό κωδικό, ένα επίπεδο σοβαρότητας, τη λειτουργία που απέτυχε, και το φύλλο όπου συνέβη, για κάθε κλήση Open, SaveAs, και Recalculate
Γιατί αποτυγχάνει ένα boolean αποτέλεσμα αποθήκευσης σε κλίμακα;
Ένα μεμονωμένο αποτυχημένο αρχείο δεν είναι το πρόβλημα που δημιουργεί ένα boolean αποτέλεσμα· χίλια από αυτά είναι. Όταν το SaveAs επιστρέφει κάτι διαφορετικό από επιτυχία για τρία αρχεία στα δέκα χιλιάδες, η επόμενη ερώτηση είναι πάντα η ίδια: είναι αυτά τα τρία επαναλήψιμα, ή χρειάζονται άνθρωπο; Ένα σφάλμα άδειας σε έναν κοινόχρηστο φάκελο δικτύου δεν είναι το ίδιο περιστατικό με έναν τύπο που η μηχανή υπολογισμού δεν μπορεί να αξιολογήσει, και κανένα από τα δύο δεν είναι το ίδιο με ένα φύλλο εργασίας που σιωπηλά υπερέβη ένα όριο μορφής. Με μόνο ένα αποτέλεσμα επιτυχία/αποτυχία για δουλειά, καθένα από αυτά γίνεται πανομοιότυπο εισιτήριο υποστήριξης, και κάποιος πρέπει να ανοίξει κάθε αρχείο με το χέρι, στο Excel, και να το κοιτάξει μέχρι η αιτία να γίνει προφανής. Εκείνη η χειροκίνητη διαλογή είναι το πραγματικό κόστος ενός boolean API, και κλιμακώνεται γραμμικά με το μέγεθος της παρτίδας, κάτι που είναι ακριβώς η ιδιότητα που δεν θέλετε από τον χειρισμό σφαλμάτων
Μέσα στο IXLSWorkbookProgress: τι φέρει ένα TXLSDiagnostic
Το IXLSWorkbookProgress είναι η διεπαφή που χρησιμοποιεί το HotXLS για να αναφέρει τόσο πώς προχωρά μια λειτουργία όσο και τι πήγε στραβά μέσα της, και τα δύο μισά μοιράζονται μία σύμβαση για έναν λόγο: και τα δύο είναι πράγματα που μια μακρόχρονη κλήση Open, SaveAs, ή Recalculate χρειάζεται να επικοινωνήσει χωρίς να εγείρει εξαίρεση στη μέση της λειτουργίας. Το μισό προόδου είναι τα OnProgress και OnProgressEx, που πυροδοτούνται με μια φάση, μια κατάσταση, και ένα ζεύγος τρέχον/σύνολο. Το μισό διαγνωστικής πληροφορίας είναι εκείνο για το οποίο μιλά αυτό το άρθρο: μια ιδιότητα Diagnostics που επιστρέφει μια λίστα TXLSDiagnostics, μια συντόμευση LastDiagnostic για την πιο πρόσφατη καταχώριση, και ένα συμβάν OnDiagnostic που πυροδοτείται τη στιγμή που δημιουργείται κάθε εγγραφή TXLSDiagnostic. Κάθε εγγραφή φέρει έναν αριθμητικό Code, ένα TXLSDiagnosticSeverity, το TXLSDiagnosticOperation που το παρήγαγε, ένα αναγνώσιμο από άνθρωπο Message, ένα SheetIndex και SheetName, και ένα NativeCode που διατηρεί όποια τιμή επιστροφής χαμηλότερου επιπέδου πυροδότησε την καταχώριση
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 με αυτόν τον τρόπο ήδη ξεπερνά ένα boolean αποτέλεσμα από μόνη της, επειδή τα Code και SheetName μετατρέπουν ένα μυστήριο σε ένα συγκεκριμένο, φιλτραρίσιμο γεγονός. Η εγγραφή TXLSDiagnostic φτάνει πιο μακριά από ό,τι τυπώνει αυτό το παράδειγμα: τα RecordId και StreamOffset υπάρχουν για δικανική ανάλυση σε επίπεδο byte μέσα σε μια ροή BIFF, και το PartName κρατά την καταχώριση zip OOXML, όπως xl/worksheets/sheet3.xml, από την οποία προήλθε ένα πρόβλημα. Αξίζει να γνωρίζετε πριν χτίσετε εργαλεία γύρω από αυτά: στην τρέχουσα έκδοση κανένα από τα ενσωματωμένα σημεία κλήσης διαγνωστικών δεν γεμίζει τα RecordId ή StreamOffset, οπότε και τα δύο παραμένουν στην προεπιλογή κατασκευαστή τους -1, που σημαίνει "δεν εφαρμόζεται" αντί για "μηδέν". Αντιμετωπίστε την απουσία τους ως φυσιολογική, όχι ως σφάλμα στον χειριστή σας
Δύο μηχανές, ένα σχήμα, μία ήσυχη διαφορά
Το HotXLS διανέμει δύο μηχανές πίσω από αυτό το ίδιο μοντέλο αναφοράς, μια πρόσοψη BIFF8 για παλαιά αρχεία .xls και μια πρόσοψη OOXML για .xlsx, και δεν εκθέτουν το IXLSWorkbookProgress με πανομοιότυπο τρόπο. Το TXLSWorkbook, η μηχανή .xls, υλοποιεί επισήμως το IXLSWorkbookProgress, οπότε μπορεί να περαστεί οπουδήποτε αναμένεται εκείνος ο τύπος διεπαφής. Το TXLSXWorkbook, η μηχανή .xlsx, εκθέτει τα ίδια μέλη Diagnostics, LastDiagnostic, OnDiagnostic, OnProgress, και OnProgressEx με πανομοιότυπα ονόματα και τύπους, αλλά ως απλή κλάση παρά ως επίσημη υλοποίηση εκείνης της διεπαφής, οπότε δεν θα ικανοποιήσει από μόνο του μια παράμετρο IXLSWorkbookProgress. Στην πράξη αυτό σπάνια έχει σημασία, επειδή οι περισσότεροι κώδικες δουλεύουν έναντι μιας συγκεκριμένης κλάσης βιβλίου εργασίας τη φορά, αλλά σημαίνει ότι δεν μπορείτε να γράψετε έναν μοναδικό βοηθό τυποποιημένο ως IXLSWorkbookProgress και να του δώσετε το αντικείμενο βιβλίου εργασίας οποιασδήποτε μηχανής εναλλάξιμα. Η μία διαφορά πεδίου που ακολουθεί απευθείας από τον διαχωρισμό μορφής είναι το PartName: μόνο η μηχανή XLSX το γεμίζει, επειδή μόνο το OOXML έχει μέρη zip προς ονομασία
Τι κάνει έναν διαγνωστικό κωδικό κάτι στο οποίο μπορείτε να διακλαδιστείτε με ασφάλεια;
Το πεδίο Code είναι το μόνο μέρος ενός διαγνωστικού που αξίζει να γράψετε σκληρά κωδικοποιημένη σύγκριση έναντι· το Message δεν είναι, επειδή το πεζό κείμενο είναι ακριβώς το είδος πράγματος που ξαναδιατυπώνεται, ξαναμεταφράζεται, ή επεκτείνεται με περισσότερη λεπτομέρεια σε μια μεταγενέστερη έκδοση χωρίς κανείς να το αντιμετωπίζει ως αλλαγή που σπάει συμβατότητα. Οι ενσωματωμένοι διαγνωστικοί κωδικοί του HotXLS ήδη διαβάζονται σαν να σχεδιάστηκαν με εκείνη τη διάκριση στο μυαλό: οι κωδικοί σχετικοί με αποθήκευση τρέχουν από 1000 έως 1005, οι κωδικοί σχετικοί με άνοιγμα κάθονται στο 1100 και 1101, οι κωδικοί σχετικοί με υπολογισμό στο 1200 και 1201, και ένας κωδικός μη υποστηριζόμενης μορφής στο 1300, με κενά αφημένα μέσα σε κάθε ζώνη αντί οι κωδικοί να τρέχουν διαδοχικά σε όλες τους. Εκείνη η απόσταση είναι αυτό που επιτρέπει σε έναν προμηθευτή να προσθέσει μια νέα λειτουργία αποτυχίας κατά την αποθήκευση στο, ας πούμε, 1006 χωρίς να επαναριθμήσει τους κωδικούς από τους οποίους ήδη εξαρτάται η δική σας δήλωση switch, και αξίζει να το ελέγξετε σε οποιοδήποτε API διαγνωστικών πριν δεσμευτείτε να ταιριάξετε σε έναν κωδικό στην παραγωγή, όχι μόνο σε αυτό. Κρατήστε έναν προεπιλεγμένο κλάδο στη δική σας λογική αποστολής ανεξάρτητα από το πόσο σταθερή φαίνεται η αρίθμηση, επειδή νέες λειτουργίες αποτυχίας είναι ακριβώς αυτό που συνεχίζει να ανακαλύπτει ένας εξελισσόμενος αναλυτής ή γραφέας. Τα NativeCode και ExceptionClass κάθονται ένα επίπεδο κάτω από το Code για όταν χρειάζεται να κλιμακώσετε: το NativeCode διατηρεί την υποκείμενη τιμή επιστροφής, ένα HRESULT από μια κλήση Structured Storage μεταξύ αυτών, και το ExceptionClass καταγράφει τον τύπο εξαίρεσης Delphi όταν ενεπλάκη μία, κάτι που συνήθως αρκεί για να ανοίξετε ένα ακριβές αίτημα υποστήριξης χωρίς να επισυνάψετε ένα πλήρες ίχνος στοίβας
Η σοβαρότητα και η λειτουργία αποφασίζουν τι κάνει στη συνέχεια ο κώδικάς σας
Η σοβαρότητα και η λειτουργία είναι αυτό που μετατρέπει ένα διαγνωστικό από γραμμή καταγραφής σε απόφαση δρομολόγησης. Το TXLSDiagnosticSeverity τρέχει Info, Warning, Error, και Fatal, και το TXLSDiagnosticOperation σημειώνει κάθε καταχώριση με την κλήση που την παρήγαγε: Open, Save, Calculate, ή Export. Οι δύο άξονες είναι ανεξάρτητοι εκ σχεδιασμού: το xlsDiagnosticUnhandledException είναι ένας σταθερός κωδικός που πυροδοτείται με το Operation ορισμένο σε όποια κλήση πράγματι το εγείρε, οπότε το Code απαντά τι πήγε στραβά ενώ το Operation ξεχωριστά απαντά πού, αντί να χρειάζεται ξεχωριστό κωδικό για μια εξαίρεση κατά το άνοιγμα έναντι μιας κατά την αποθήκευση. Αυτή η συνθεσιμότητα είναι επίσης αυτό που κάνει τη δρομολόγηση μηχανική: καταγράψτε μια προειδοποίηση και συνεχίστε, μια αποθήκευση ακυρωμένη μέσω της σημαίας Aborted είναι ένα τυπικό παράδειγμα· μετρήστε ένα σφάλμα και κρατήστε την παρτίδα να τρέχει, ένα φύλλο εργασίας που απέτυχε να σειριοποιηθεί είναι ένα τυπικό παράδειγμα· σταματήστε την παρτίδα σε σοβαρότητα fatal, επειδή εκείνο το επίπεδο σημαίνει ότι μια μη χειρισμένη εξαίρεση έχει ήδη ξετυλίξει την κλήση και η συνέχιση κινδυνεύει να δουλεύει από μια μισο-ενημερωμένη κατάσταση. Μια ειλικρινής επιφύλαξη: το Info υπάρχει στην απαρίθμηση ως η προεπιλογή με την οποία ξεκινά ένα φρέσκο TXLSDiagnostic, αλλά κάθε σημείο κλήσης διαγνωστικού ενσωματωμένο στη σημερινή έκδοση του HotXLS εγείρει μόνο ποτέ Warning, Error, ή Fatal· το Info είναι κρατημένο για μελλοντική χρήση, όχι κάτι που εκπέμπει σήμερα η μηχανή
// 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 σε μια διοχέτευση παρτίδας
Η δειγματοληψία του Diagnostics μετά από κάθε κλήση λειτουργεί για ένα μεμονωμένο αρχείο· σταματά να λειτουργεί μόλις επιστρέψετε σε εκείνη τη νυχτερινή παρτίδα δέκα χιλιάδων, επειδή το Diagnostics καθαρίζεται στην αρχή κάθε κλήσης Open, SaveAs, και Recalculate. Διαβάστε το μετά το τρίτο αρχείο σε έναν βρόχο και βλέπετε μόνο τα διαγνωστικά του τρίτου αρχείου· ό,τι ανέφεραν τα πρώτα δύο αρχεία έχει ήδη χαθεί. Το OnDiagnostic το λύνει μετατρέποντας τη συλλογή σε ροή: εγγραφείτε μία φορά πριν ξεκινήσει ο βρόχος, και ο ίδιος χειριστής πυροδοτείται για κάθε αρχείο, με τη σειρά, με το όνομα αρχείου ακόμη στο πεδίο εφαρμογής μέσω ενός πεδίου στιγμιότυπου
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;
Τι κοστίζει πράγματι το callback
Το OnDiagnostic είναι φθηνό για δομικό λόγο: πυροδοτείται μόνο όταν κάτι είναι ήδη λάθος, και το λάθος είναι σπάνιο σε σύγκριση με τον αριθμό κελιών, γραμμών, ή φύλλων εργασίας που κρατά ένα βιβλίο εργασίας. Αντιπαραβάλετε αυτό με τα OnProgress και OnProgressEx, που αναφέρουν συνηθισμένη πρόοδο και έπρεπε να σχεδιαστούν γύρω από τη συχνότητα κλήσεων εξαρχής. Το HotXLS πυροδοτεί πρόοδο σε επίπεδο φύλλου εργασίας μία φορά ανά φύλλο κατά τη διάρκεια των Open και SaveAs, όχι μία φορά ανά κελί ή γραμμή, κάτι που είναι αυτό που κρατά μικρό το πρόσθετο κόστος ανά κλήση ακόμη και σε βιβλία εργασίας με εκατομμύρια κελιά· το Recalculate πάει παραπέρα και περιορίζει το δικό του συμβάν προόδου σε περίπου κάθε τέσσερα τοις εκατό του γραφήματος εξαρτήσεων, οπότε ένας πλήρης επανυπολογισμός σας δίνει έναν παλμό αντί να πλημμυρίζει το νήμα UI σας με συμβάντα. Η διαγνωστική πληροφορία δεν χρειάστηκε καθόλου εκείνον τον περιορισμό, επειδή ο αριθμός συμβάντων περιορίζεται από τον αριθμό πραγματικών προβλημάτων, όχι από το μέγεθος του αρχείου
Το ένα μέρος όπου η απόδοση εξακολουθεί να εξαρτάται από εσάς είναι μέσα στον ίδιο τον χειριστή. Το OnDiagnostic πυροδοτείται σύγχρονα, στο νήμα που εκτελεί το Open, SaveAs, ή Recalculate, οπότε ένας χειριστής που μπλοκάρει, μια σύγχρονη εγγραφή σε μια απομακρυσμένη υπηρεσία καταγραφής για παράδειγμα, γίνεται μέρος του χρόνου ρολογιού τοίχου εκείνης της κλήσης. Για ένα μεμονωμένο αρχείο αυτό είναι αόρατο. Πολλαπλασιασμένο σε μια παρτίδα δέκα χιλιάδων αρχείων είναι η διαφορά μεταξύ μιας εργασίας που τελειώνει κατά τη διάρκεια της νύχτας και μιας που εξακολουθεί να τρέχει το μεσημέρι, οπότε αποθηκεύστε προσωρινά ό,τι χρειάζεται να κάνει ο χειριστής και εκκενώστε το ασύγχρονα αντί να κάνετε το αργό μέρος ενσωματωμένο
Η δομημένη διαγνωστική πληροφορία είναι πιο πολύτιμη ακριβώς εκεί όπου ένα boolean αποτέλεσμα είναι πιο αδύναμο, σε ροές εργασίας που αγγίζουν πολλά αρχεία αντί για ένα. Μια διοχέτευση ελέγχου και μετατροπής βιβλίου εργασίας είναι το σαφέστερο παράδειγμα: αντί να καταγράφετε ένα γυμνό επιτυχία/αποτυχία ανά αρχείο, επισυνάψτε τη λίστα Diagnostics κάθε αρχείου στην εγγραφή ελέγχου του, και η αναφορά σάς λέει όχι μόνο τι απέτυχε αλλά γιατί, κάτι που είναι το μεγαλύτερο μέρος αυτού που προσπαθεί να πετύχει εξαρχής το άρθρο μας για το χτίσιμο ενός πάγκου εργασίας ελέγχου και μετατροπής βιβλίου εργασίας. Το ίδιο ζευγάρωμα προόδου και διαγνωστικής πληροφορίας ανήκει επίσης σε οποιαδήποτε ροή εργασίας που ήδη χρειάζεται αναφορά προόδου για τους δικούς της λόγους, κάτι που είναι ακριβώς το έδαφος που καλύπτει ο οδηγός μας για την απόδοση μεγάλων βιβλίων εργασίας στο HotXLS, όπου μια μακρά κλήση Open ή SaveAs είναι αρκετά συνηθισμένη ώστε το OnProgress να είναι ήδη καλωδιωμένο και το OnDiagnostic να είναι μια φυσική, σχεδόν δωρεάν προσθήκη δίπλα του
Τίποτα από αυτά δεν απαιτεί εγκατεστημένο Excel πουθενά στη διοχέτευση, και τίποτα από αυτά δεν απαιτεί τη σύλληψη μιας γενικής εξαίρεσης και το να μαντεύετε τι σήμαινε. Το IXLSWorkbookProgress και τα μέλη του Diagnostics, LastDiagnostic, και OnDiagnostic είναι μέρος του τυπικού εξαρτήματος HotXLS για Delphi και C++Builder, μαζί με την πλήρη αναφορά διαγνωστικών κωδικών και το υπόλοιπο της επιφάνειας Open, SaveAs, και Recalculate που διατρέχει αυτό το άρθρο