Jalankan konversi batch semalaman atas sepuluh ribu spreadsheet, dan pada pagi hari tiga di antaranya kembali False. Itulah seluruh postmortem yang diberikan sebuah hasil save boolean kepada Anda: sebuah hitungan kegagalan, tanpa apa pun tentang file mana, sheet mana, atau penyebab mana dari selusin kemungkinan yang bertanggung jawab. HotXLS, komponen native losLab untuk Delphi dan C++Builder untuk file Excel, menggantikan satu bit tunggal itu dengan diagnostik terstruktur. Interface IXLSWorkbookProgress mengekspos sebuah daftar Diagnostics dan sebuah event OnDiagnostic yang melaporkan sebuah kode numerik stabil, sebuah level severity, operasi yang gagal, dan sheet tempat kejadian itu terjadi, untuk setiap pemanggilan Open, SaveAs, dan Recalculate
Mengapa hasil save boolean gagal pada skala besar?
Satu file yang gagal bukanlah masalah yang diciptakan sebuah hasil boolean; seribu file yang gagal itulah masalahnya. Ketika SaveAs mengembalikan sesuatu selain sukses untuk tiga file dari sepuluh ribu, pertanyaan berikutnya selalu sama: apakah ketiganya bisa dicoba ulang, atau butuh manusia? Sebuah error izin pada network share bukan insiden yang sama dengan sebuah formula yang tidak bisa dievaluasi mesin kalkulasi, dan keduanya juga bukan sama dengan sebuah worksheet yang diam-diam melebihi batas format. Dengan hanya hasil pass/fail untuk dikerjakan, setiap kasus itu menjadi tiket dukungan yang identik, dan seseorang harus membuka setiap file dengan tangan, di Excel, dan menatapnya sampai penyebabnya jadi jelas. Triase manual itulah biaya sesungguhnya sebuah API boolean, dan skalanya bertambah linear dengan ukuran batch, yang persis merupakan sifat yang tidak Anda inginkan dari penanganan error
Di dalam IXLSWorkbookProgress: apa yang dibawa TXLSDiagnostic
IXLSWorkbookProgress adalah interface yang digunakan HotXLS untuk melaporkan baik bagaimana kemajuan sebuah operasi maupun apa yang salah di dalamnya, dan kedua separuh itu berbagi satu kontrak karena sebuah alasan: keduanya adalah hal-hal yang perlu dikomunikasikan sebuah pemanggilan Open, SaveAs, atau Recalculate yang berjalan lama tanpa memunculkan exception di tengah operasi. Separuh progress adalah OnProgress dan OnProgressEx, terpicu dengan sebuah phase, sebuah state, dan sepasang current/total. Separuh diagnostik adalah yang dibahas artikel ini: sebuah properti Diagnostics yang mengembalikan sebuah daftar TXLSDiagnostics, sebuah shortcut LastDiagnostic untuk entri paling baru, dan sebuah event OnDiagnostic yang terpicu saat setiap record TXLSDiagnostic dibuat. Setiap record membawa sebuah Code numerik, sebuah TXLSDiagnosticSeverity, TXLSDiagnosticOperation yang menghasilkannya, sebuah Message yang bisa dibaca manusia, sebuah SheetIndex dan SheetName, dan sebuah NativeCode yang mempertahankan nilai kembali tingkat-lebih-rendah apa pun yang memicu entri tersebut
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;
Membaca Diagnostics seperti ini saja sudah mengalahkan hasil boolean, karena Code dan SheetName mengubah sebuah misteri menjadi sebuah fakta spesifik yang bisa difilter. Record TXLSDiagnostic menjangkau lebih jauh dari yang dicetak contoh ini: RecordId dan StreamOffset ada untuk forensik tingkat-byte di dalam sebuah stream BIFF, dan PartName memegang entri zip OOXML, seperti xl/worksheets/sheet3.xml, dari mana sebuah masalah berasal. Layak diketahui sebelum Anda membangun tooling di sekitarnya: pada rilis saat ini tidak satu pun titik pemanggilan diagnostik bawaan mengisi RecordId atau StreamOffset, sehingga keduanya tetap pada default constructor-nya yaitu -1, berarti "tidak berlaku" bukan "nol". Perlakukan ketidakhadirannya sebagai normal, bukan sebagai bug di handler Anda
Dua mesin, satu bentuk, satu perbedaan diam-diam
HotXLS mengirimkan dua mesin di balik model pelaporan yang sama ini, sebuah facade BIFF8 untuk file .xls lawas dan sebuah facade OOXML untuk .xlsx, dan keduanya tidak mengekspos IXLSWorkbookProgress secara identik. TXLSWorkbook, mesin .xls, secara formal mengimplementasikan IXLSWorkbookProgress, sehingga bisa diteruskan ke mana pun tipe interface itu diharapkan. TXLSXWorkbook, mesin .xlsx, mengekspos anggota Diagnostics, LastDiagnostic, OnDiagnostic, OnProgress, dan OnProgressEx yang sama dengan nama dan tipe identik, tetapi sebagai kelas polos alih-alih implementasi formal interface itu, sehingga tidak akan memenuhi sebuah parameter IXLSWorkbookProgress dengan sendirinya. Dalam praktiknya ini jarang penting, karena kebanyakan kode bekerja terhadap satu kelas workbook konkret pada satu waktu, tetapi ini berarti Anda tidak bisa menulis satu helper tunggal bertipe IXLSWorkbookProgress dan menyerahkannya objek workbook mesin mana pun secara bergantian. Satu perbedaan field yang mengikuti langsung dari pemisahan format adalah PartName: hanya mesin XLSX yang mengisinya, karena hanya OOXML yang memiliki bagian zip untuk dinamai
Apa yang membuat sebuah kode diagnostik menjadi sesuatu yang aman untuk dicabangkan?
Field Code adalah satu-satunya bagian dari sebuah diagnostik yang layak di-hardcode sebuah perbandingan terhadapnya; Message tidak, karena prosa adalah persis jenis hal yang bisa diubah kata-katanya, diterjemahkan ulang, atau diperluas dengan lebih banyak detail pada rilis belakangan tanpa ada yang memperlakukannya sebagai perubahan yang melanggar (breaking change). Kode diagnostik bawaan HotXLS sudah terbaca seolah dirancang dengan perbedaan itu dalam pikiran: kode terkait-save berjalan 1000 hingga 1005, kode terkait-open berada di 1100 dan 1101, kode terkait-calculate di 1200 dan 1201, dan sebuah kode format-tidak-didukung di 1300, dengan celah yang ditinggalkan di dalam setiap pita alih-alih kode yang berjalan berurutan di semuanya. Jarak itulah yang memungkinkan sebuah vendor menambahkan mode kegagalan save-time baru pada, katakanlah, 1006 tanpa menomori ulang kode yang sudah diandalkan switch statement Anda, dan ini layak diperiksa pada API diagnostik apa pun sebelum Anda berkomitmen mencocokkan sebuah kode dalam produksi, bukan hanya yang satu ini. Pertahankan sebuah cabang default dalam logika dispatch Anda sendiri terlepas dari seberapa stabil penomoran itu terlihat, karena mode kegagalan baru persis merupakan apa yang terus ditemukan sebuah parser atau writer yang berkembang. NativeCode dan ExceptionClass berada satu lapis di bawah Code untuk saat Anda perlu meningkatkan eskalasi: NativeCode mempertahankan nilai kembali di bawahnya, sebuah HRESULT dari sebuah pemanggilan Structured Storage di antaranya, dan ExceptionClass mencatat tipe exception Delphi ketika salah satu terlibat, yang biasanya cukup untuk membuka sebuah permintaan dukungan yang presisi tanpa melampirkan sebuah stack trace penuh
Severity dan operation memutuskan apa yang dilakukan kode Anda selanjutnya
Severity dan operation adalah yang mengubah sebuah diagnostik dari sebuah baris log menjadi sebuah keputusan routing. TXLSDiagnosticSeverity berjalan Info, Warning, Error, dan Fatal, dan TXLSDiagnosticOperation menandai setiap entri dengan pemanggilan yang menghasilkannya: Open, Save, Calculate, atau Export. Kedua sumbu itu independen secara desain: xlsDiagnosticUnhandledException adalah satu kode tetap yang terpicu dengan Operation diatur ke pemanggilan mana pun yang benar-benar memunculkannya, sehingga Code menjawab apa yang salah sementara Operation secara terpisah menjawab di mana, alih-alih membutuhkan kode berbeda untuk sebuah exception saat open versus saat save. Komposabilitas itu juga yang membuat routing menjadi mekanis: catat sebuah warning dan lanjutkan, sebuah save yang dibatalkan lewat flag Aborted adalah contoh tipikal; hitung sebuah error dan biarkan batch tetap berjalan, sebuah worksheet yang gagal diserialisasi adalah contoh tipikal; hentikan batch pada severity fatal, karena level itu berarti sebuah exception yang tidak tertangani sudah membatalkan pemanggilan dan melanjutkan berisiko bekerja dari state yang setengah-terupdate. Satu catatan jujur: Info ada dalam enum sebagai default yang dimulai sebuah TXLSDiagnostic baru, tetapi setiap titik pemanggilan diagnostik yang dibangun ke dalam rilis HotXLS hari ini hanya pernah memunculkan Warning, Error, atau Fatal; Info disediakan untuk penggunaan mendatang, bukan sesuatu yang dipancarkan mesin ini hari ini
// 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;
Menghubungkan OnDiagnostic ke dalam pipeline batch
Polling Diagnostics setelah setiap pemanggilan bekerja untuk satu file; berhenti bekerja begitu Anda kembali ke batch semalaman sepuluh ribu file itu, karena Diagnostics dikosongkan pada awal setiap pemanggilan Open, SaveAs, dan Recalculate. Baca setelah file ketiga dalam sebuah loop dan Anda hanya melihat diagnostik file ketiga; apa pun yang dilaporkan dua file pertama sudah hilang. OnDiagnostic menyelesaikan itu dengan mengubah koleksi menjadi sebuah stream: berlangganan sekali sebelum loop dimulai, dan handler yang sama terpicu untuk setiap file, secara berurutan, dengan nama file masih dalam lingkup lewat sebuah field 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;
Apa sebenarnya biaya callback ini
OnDiagnostic murah karena alasan struktural: ia hanya terpicu ketika sesuatu memang sudah salah, dan salah itu jarang dibandingkan jumlah sel, baris, atau worksheet yang dimiliki sebuah workbook. Bandingkan itu dengan OnProgress dan OnProgressEx, yang melaporkan kemajuan rutin dan harus dirancang di sekitar frekuensi pemanggilan sejak awal. HotXLS memicu progress tingkat-worksheet sekali per sheet selama Open dan SaveAs, bukan sekali per sel atau baris, dan itulah yang menjaga overhead per-pemanggilan tetap kecil bahkan pada workbook dengan jutaan sel; Recalculate melangkah lebih jauh dan membatasi event progress-nya sendiri kira-kira setiap empat persen dari dependency graph, sehingga sebuah rekalkulasi penuh memberi Anda sebuah heartbeat alih-alih membanjiri UI thread Anda dengan event. Diagnostik tidak membutuhkan pembatasan semacam itu, karena jumlah event dibatasi oleh jumlah masalah sesungguhnya, bukan oleh ukuran file
Satu tempat performa masih bergantung pada Anda adalah di dalam handler itu sendiri. OnDiagnostic terpicu secara sinkron, pada thread yang menjalankan Open, SaveAs, atau Recalculate, sehingga sebuah handler yang memblokir, misalnya sebuah penulisan sinkron ke sebuah layanan logging jarak jauh, menjadi bagian dari waktu wall-clock pemanggilan itu. Untuk satu file itu tidak terlihat. Dikalikan di seluruh batch sepuluh ribu file itulah perbedaan antara sebuah job yang selesai semalaman dan satu yang masih berjalan saat makan siang, jadi buffer apa yang perlu dilakukan handler tersebut dan flush secara asinkron alih-alih melakukan bagian lambat secara inline
Diagnostik terstruktur paling berharga persis di mana sebuah hasil boolean paling lemah, dalam workflow yang menyentuh banyak file alih-alih satu. Sebuah pipeline audit dan konversi workbook adalah contoh paling jelas: alih-alih mencatat sekadar pass/fail per file, lampirkan daftar Diagnostics setiap file ke record audit-nya, dan laporan itu memberi tahu Anda bukan hanya apa yang gagal tetapi mengapa, yang merupakan sebagian besar dari apa yang coba dicapai artikel kami tentang membangun sebuah workbench audit dan konversi workbook sejak awal. Pemasangan progress dan diagnostik yang sama juga cocok pada workflow apa pun yang sudah membutuhkan pelaporan progress demi dirinya sendiri, yang persis merupakan wilayah yang dibahas panduan kami tentang performa workbook besar di HotXLS, di mana sebuah pemanggilan Open atau SaveAs yang panjang cukup umum sehingga OnProgress sudah terhubung dan OnDiagnostic adalah tambahan alami yang nyaris gratis di sebelahnya
Tidak satu pun dari ini membutuhkan Excel terinstal di mana pun dalam pipeline, dan tidak satu pun membutuhkan menangkap sebuah exception generik dan menebak apa maksudnya. IXLSWorkbookProgress beserta anggota Diagnostics, LastDiagnostic, dan OnDiagnostic-nya adalah bagian dari HotXLS Component standar untuk Delphi dan C++Builder, berdampingan dengan referensi kode diagnostik lengkap dan sisa permukaan Open, SaveAs, dan Recalculate yang dibahas artikel ini