vga написал(а):
Интересно, что у вас за разработка, если возникла потребность анализа ошибок таких разноплановых ситуаций?
Ничего такого грандиозного...

С одной стороны, ABAP не особенно подталкивает к повторному использованию кода, конкретно более успешному в этом плане ООП по сравнению с процедурным подходом. С другой стороны, даже имеющийся набор OLE-классов каждый раз порождает overhead. В итоге, как заметил, сотрудники моего отдела в каждой выгрузке/загрузке в/из Excel пишут один и тот же код... отличающийся, пожалуй, только специфическими для каждой программы багами.
Идея состояла в реализации библиотеки классов для упрощения выгрузки/загрузки простых отчетов Excel.
ДелегированиеНад каждым необходимым OLE-классом делается обертка, которая, во-первых, дает более высокоуровневый интерфейс взаимодействия, а во-вторых, инкапсулирует все, что повторяется в любом OLE-взаимодействии. Например, повторяющимся действием для OLE является освобождение handler, по аналогии с java.io.Closeable можно оформить интерфейсом:
Цитата:
* Закрытие
interface ICloseable.
methods close.
endinterface.
Данный интерфейс должны реализовывать все классы, работающие с OLE. Пример самих классов-оберток приведу ниже.
Диагностика ошибокOLE-классы с жутко неудобной диагностикой ошибок. Вместо того, чтобы уведомлять о возникшей ошибке или пробрасывать по стеку вызова возникшее исключение (как происходит в Java), клиент вынужден после каждого чиха спрашивать: ошибка произошла, а сейчас, и теперь тоже все OK? Реализация исключений в ABAP-е не очень чтобы очень, а вот события для этой цели подошли как нельзя кстати.
По сути обычный шаблон проектирования Observer (Издатель/Подписчик):
Code:
* Уведомитель исключения
class ExceptionEventPublisher definition abstract.
public section.
events exception exporting value(message) type string.
protected section.
methods:
* Проверить наличие ошибки
check,
* Зажечь событие об ошибке
raise abstract.
endclass.
class ExceptionEventPublisher implementation.
method check.
if sy-subrc <> 0.
call method raise.
endif.
endmethod.
endclass.
Этот абстрактный уведомитель конкретизируется в наследнике, несущем конкретику OLE:
Code:
* Уведомитель Ole-исключения
class OleExceptionEventPublisher definition
inheriting from ExceptionEventPublisher.
protected section.
methods raise redefinition.
endclass.
class OleExceptionEventPublisher implementation.
method raise.
data message type string.
concatenate
'Error in OLE call'
sy-msgli
into message
separated by ' '.
raise event exception exporting message = message.
endmethod.
endclass.
Далее любой класс, работающий с OLE, с одной стороны, наследует класс OleExceptionEventPublisher - оповещает об ошибках делегируемого OLE-класса, а во-вторых, сам подписывается на события создаваемых изнутри него классов (так приложение прослушивает события книг, книга - листов, лист - ячеек и т.п.) и в случае возникновения, пробрасывает выше как собственное.
Самый простой пример (выдрал все неважное из класса):
Code:
* Ячейка
class Cell definition
inheriting from OleExceptionEventPublisher.
public section.
interfaces ICloseable.
aliases close for ICloseable~close.
methods:
* Конструктор
constructor
importing _cell type ole2_object,
* Получить значение
getValue
returning value(result) type string,
* Установить значение
setValue
importing value type string.
private section.
data:
cell type ole2_object.
endclass.
class Cell implementation.
method constructor.
call method super->constructor.
cell = _cell.
endmethod.
method getValue.
field-symbols: <_value> type any.
assign result to <_value>.
get property of cell 'value' = <_value>.
call method check.
endmethod.
method setValue.
field-symbols: <_value> type any.
assign value to <_value>.
set property of cell 'value' = <_value>.
call method check.
endmethod.
* override
method ICloseable~close.
if not cell is initial.
free object cell.
endif.
endmethod.
endclass.
С классом Cell в основном работает только класс Sheet: создает его при обработке ячеек, закрывает его (незаметно для клиента) по окончанию обработки вызовом метода close, подписывается на его события об ошибках, пробрасывая выше как собственное при возникновении и т.п.:
Code:
* Лист
class Sheet definition
inheriting from OleExceptionEventPublisher.
public section.
interfaces ICloseable.
aliases close for ICloseable~close.
class-data MAX_LINE_WIDTH type i value 256 read-only.
methods:
* Конструктор
constructor
importing
_sheet type ole2_object,
* Установить название
setName
importing name type string,
* Получить ячейку
getCell
importing
rowIndex type i
columnIndex type i
returning value(result) type ref to Cell.
protected section.
* Обработка событий ячейки
methods catchCellException for event exception of Cell
importing sender message.
private section.
data sheet type ole2_object.
endclass.
class Sheet implementation.
method constructor.
call method super->constructor.
sheet = _sheet.
endmethod.
method setName.
field-symbols: <_name> type any.
assign name to <_name>.
set property of sheet 'name' = <_name>.
call method check.
endmethod.
method getCell.
data range type ole2_object.
get property of sheet 'cells' = range
exporting
#1 = rowIndex
#2 = columnIndex.
call method check.
create object result
exporting _cell = range.
set handler catchCellException for result.
endmethod.
method catchCellException.
raise event exception exporting message = message.
endmethod.
* override
method ICloseable~close.
if not sheet is initial.
free object sheet.
endif.
endmethod.
endclass.
Писатели, читателиПо аналогии с Java создал классы, умеющие писать/читать содержимое по соответствующему OLE-каналу.
Code:
* Писатель
interface IWriter.
interfaces ICloseable.
aliases close for ICloseable~close.
data:
headerLine type table of string.
methods:
* Записать строку (ListString type table of string)
writeLine
importing line type ListString,
* Установить заголовок
setHeader
importing header type ListString,
* Получить индекс текущего ряда
getCurrentRowIndex
returning value(result) type i.
endinterface.
* Читатель
interface IReader.
interfaces ICloseable.
aliases close for ICloseable~close.
methods:
* Прочитать строку (ListString type table of string)
readLine exporting line type ListString,
* Получить индекс текущего ряда
getCurrentRowIndex
returning value(result) type i.
endinterface.
Соответственно, классы, наследующие данные интерфейсы: SheetWriter/SheetReader (умеет писать/читать с листа), SheetWriter/SheetReader - с книги (писатель учитывает ограничения листа по 64K строк, при необходимости создает новый лист в рамках книги, освобождает ресурсы обработанных листов и т.п.) и другие.
ИспользованиеВ итоге простой экспорт выглядит так (не показан только перехват исключений):
Code:
include zbc_excel_io.
data path type string.
path = 'c:\temp\book.xls'.
* Пример генерации XLS-файла
* 1. Создаем XLS-контейнер
data excel type ref to Excel.
create object excel.
* 2. Создаем писатель XLS-документа
data writer type ref to ExcelWriter.
create object writer
exporting _excel = excel.
* 3. Устанавливаем шапку таблицы
data header type table of string.
append 'Заголовок 1' to header.
append 'Заголовок 2' to header.
append 'Заголовок 3' to header.
append 'Заголовок 4' to header.
call method writer->setHeader
exporting header = header.
* 4. Формируем строку таблицы
data line type table of string.
append 'Значение 1' to line.
append 'Значение 2' to line.
append 'Значение 3' to line.
append 'Значение 4' to line.
* 5. Записываем строку 20 раз
do 20 times.
call method writer->writeLine
exporting line = line.
enddo.
* 6. Сохраняем и закрываем XLS-документ
call method writer->close
exporting path = path.
Что мы получили:
- мы избавлены от необходимости работы с OLE-классами напрямую;
- мы избавлены от необходимости перехвата ошибок после каждого действия, достаточно один раз подписаться на событие exception писателя writer;
- мы избавлены от низкоуровневой работы с таблицами, к примеру, вся работа с ячейками спрятана внутри используемых нами классов, мы же просто командуем на запись, передавая строку таблицы Excel;
- мы избавлены от необходимости явно закрывать handler каждого OLE-канала, все это делается автоматически;
- мы получаем библиотеку со специфическими ошибками, но не множественными, если писать данный функционал каждый раз снова и снова.
vga написал(а):
Наверно как минимум полгода ABAP-а.
Наверно я как-то излишне сильно раздул щеки...
Нет, просто некоторая оптимизация работы отдела, что-то около 3 дней... столько же - научить своих пользоваться данными классами.