Техническа статия

Структурирана диагностика вместо булеви резултати в HotXLS

Пуснете пакетно преобразуване на десет хиляди електронни таблици през нощта и сутринта три от тях се върнат с False. Това е целият анализ след инцидента, който булевият резултат от запис ви предоставя: брой неуспешни операции, без информация кой файл, кой лист или коя от дузина възможни причини е отговорна. HotXLS, нативният компонент на losLab за Delphi и C++Builder за файлове на Excel, заменя този единствен бит със структурирана диагностика. Интерфейсът IXLSWorkbookProgress предоставя списък Diagnostics и събитие OnDiagnostic, които съобщават стабилен числов код, ниво на тежест, неуспешната операция и листа, в който е възникнала, при всяко извикване на Open, SaveAs и Recalculate

Защо булевият резултат от запис не издържа при голям мащаб

Един неуспешен файл не е проблемът, който създава булевият резултат; проблемът са хиляда такива файла. Когато SaveAs върне нещо различно от успех за три файла от десет хиляди, следващият въпрос винаги е един и същ: тези три файла могат ли да се обработят повторно, или е нужен човек? Грешка в правата за мрежов дял не е същият инцидент като формула, която механизмът за изчисление не може да оцени, а нито едното не е същото като лист, който тихомълком е надхвърлил ограничение на формата. Когато разполагате само с резултат за успех или неуспех, всеки от тези случаи се превръща в еднакъв билет за поддръжка и някой трябва да отвори всеки файл ръчно в Excel и да го разглежда, докато причината стане очевидна. Този ръчен първоначален анализ е реалната цена на булевия 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 по този начин вече е по-полезно от булев резултат, защото Code и SheetName превръщат загадката в конкретен факт, който може да се филтрира. Записът TXLSDiagnostic съдържа повече от отпечатаното в този пример: RecordId и StreamOffset са предназначени за байтово ниво на анализ в 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 за диагностика, преди да обвържете производствения код с определен код, а не само в този 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; пребройте грешката и оставете пакета да продължи, като типичен пример е лист, който не е успял да се сериализира; спрете пакета при фатална тежест, защото това ниво означава, че необработено изключение вече е прекъснало извикването и продължаването крие риск от работа с частично обновено състояние. Има една честна уговорка: 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;

Каква е реалната цена на обратното извикване

OnDiagnostic е евтино по структурна причина: задейства се само когато вече има проблем, а проблемите са редки в сравнение с броя клетки, редове или листове в една книга. Сравнете това с OnProgress и OnProgressEx, които съобщават нормалния напредък и още отначало трябваше да бъдат проектирани спрямо честотата на извикванията. HotXLS задейства напредъка на ниво лист веднъж за всеки лист по време на Open и SaveAs, а не веднъж за всяка клетка или ред, което поддържа малки разходите за всяко извикване дори при книги с милиони клетки; Recalculate отива още по-далеч и ограничава собственото си събитие за напредък до приблизително всеки четири процента от графа на зависимостите, така че пълното преизчисление ви дава пулс, вместо да претоварва нишката на потребителския интерфейс със събития. За диагностиката не е нужна такава ограничителна логика, защото броят на събитията се определя от действителните проблеми, а не от размера на файла

Единственото място, където производителността все още зависи от вас, е самият обработчик. OnDiagnostic се задейства синхронно в нишката, която изпълнява Open, SaveAs или Recalculate, затова обработчик, който блокира, например чрез синхронен запис към отдалечена услуга за журнал, става част от изминалото време на това извикване. При един файл това не се забелязва. Умножено по пакет от десет хиляди файла, то е разликата между задача, която приключва през нощта, и задача, която още работи по обяд, затова буферирайте необходимото в обработчика и го изпращайте асинхронно, вместо да изпълнявате бавната част в същата нишка

Структурираната диагностика е най-ценна точно там, където булевият резултат е най-слаб: в работни процеси, които обработват много файлове, а не един. Най-ясният пример е конвейерът за одит и преобразуване на книги: вместо да записвате обикновен резултат за успех или неуспех за всеки файл, добавете списъка Diagnostics на всеки файл към неговия одитен запис и отчетът ще ви каже не само какво се е провалило, а и защо, което е основната цел на нашата статия за създаване на работна среда за одит и преобразуване на книги. Същото съчетание от напредък и диагностика принадлежи и на всеки работен процес, който така или иначе се нуждае от отчитане на напредъка, което е точно територията, разгледана в нашето ръководство за производителността при големи книги в HotXLS, където продължително извикване на Open или SaveAs се среща достатъчно често, за да е вече свързан OnProgress, а OnDiagnostic е естествено и почти безплатно допълнение до него

Нищо от това не изисква Excel да е инсталиран някъде в конвейера и нищо не изисква да прихващате общо изключение и да гадаете какво означава. IXLSWorkbookProgress и членовете му Diagnostics, LastDiagnostic и OnDiagnostic са част от стандартния компонент HotXLS за Delphi и C++Builder, заедно с пълния справочник на диагностичните кодове и останалата повърхност от Open, SaveAs и Recalculate, разгледана в тази статия