Маленькие хитрости

Тема в разделе "Lotus - Программирование", создана пользователем Yakov, 28 июл 2009.

  1. Yakov

    Yakov Гость

    Перебор коллекции документов - типичная задача.
    У меня обычно это выглядит примерно так:
    Код (Text):
    Dim collection As NotesDocumentCollection
    Dim document As NotesDocument

    Set collection = database.Search(...)

    If collection Is Nothing Then Exit Sub 'на всякий случай
    If collection.Count = 0 Then Exit Sub

    Set document = collection.GetFirstDocument()
    While Not document Is Nothing
    Call processDocument(document)
    Set document = collection.GetNextDocument(document)
    Wend
    Очень много кода, не правда ли? Вот бы сократить его до такого:
    Код (Text):
    Dim iterator As CollectionIterator

    Set iterator = New CollectionIterator(database.Search(...))

    While iterator.hasNext()
    Call processDocument(iterator.next())
    Wend
    Сказано - сделано!
    Код (Text):
    Public Class CollectionIterator

    Private collection As Variant
    Private cursor As Variant

    Public Sub new(collection As Variant)
    Set Me.collection = Nothing
    Set cursor = Nothing
    If collection Is Nothing Then Exit Sub
    If collection.count = 0 Then Exit Sub
    Set Me.collection = collection
    Set cursor = collection.getFirstDocument()
    End Sub

    Public Function hasNext() As Boolean
    hasNext = Not cursor Is Nothing
    End Function

    Public Function next() As Variant
    Dim tmp As Variant
    If Not hasNext() Then
    Error 188, "Collection has no more elements."   'ErrIllegalUseOfFunction
    End If
    Set tmp = cursor
    Set cursor = collection.getNextDocument(cursor)
    Set Me.next = tmp
    End Function

    End Class
     
  2. allex

    allex Гость

    А что дает это нагромождение ?
     
  3. ToxaRat

    ToxaRat Чёрный маг
    Lotus team

    Регистрация:
    6 ноя 2007
    Сообщения:
    3.046
    Симпатии:
    18
    :)
    от жеж сократил....
    со стороны кодинда да, неоспоримо сокращения на лицо особенно если заюзать раз 20 то может код даже в пару раз меньше будет
    однако со стороны откомпиленного кода мы получаем увеличение испольняемого кода в разы да еще и его замедление из-за постоянного обращения к стеку и прыганься по функциям...

    кстате вместо
    Код (Text):
    While Not document Is Nothing
    предпочитаю
    Код (Text):
    do until document Is Nothing
     
  4. Omh

    Omh Lotus team
    Lotus team

    Регистрация:
    4 июл 2007
    Сообщения:
    2.210
    Симпатии:
    0
    Обоснуй ;)
     
  5. ToxaRat

    ToxaRat Чёрный маг
    Lotus team

    Регистрация:
    6 ноя 2007
    Сообщения:
    3.046
    Симпатии:
    18
    так короче ;)
     
  6. Omh

    Omh Lotus team
    Lotus team

    Регистрация:
    4 июл 2007
    Сообщения:
    2.210
    Симпатии:
    0
    А ещё можно выйти с помощью
    Код (Text):
    Exit Do
    если приспичит...
     
  7. turumbay

    Регистрация:
    13 мар 2009
    Сообщения:
    625
    Симпатии:
    2
    Ирония напрасна. Короче код - меньше ошибок. И читается он легче.
    "Написать код, понятный компьютеру, может каждый, но только хорошие программисты пишут код, понятный людям." - М. Фаулер.
    А замечания о производительности так просто чушь. Обход коллекции - операция затратная и лишнее обращение к стеку вызовов - чих: Вводная: сервер в локалке, коллекция 218 документов.
    Вариант №1 - традиционный обход( while ... wend ). Вариант №2 - итератор от Yakov
    запуск №1
    Вариант 1: 0.69с
    Вариант 2: 0.72с
    запуск №2
    Вариант 1: 0.78с
    Вариант 2: 0,66с
    to Yakov: вот только тип метода next() таки стоит поменять на NotesDocument и предоставить компилятору проверять типы.
    Декларация Function processDoc( i_doc As NotesDocument ) ругаеца при включенном option declare - и прально делает.
     
  8. Yakov

    Yakov Гость

    turumbay, спасибо за тест.

    На самом деле, CollectionIterator имеет (абстрактный) суперкласс Iterator:
    Код (Text):
    Public Class Iterator
    Public Function hasNext() As Boolean
    Public Function next() As Variant
    End Class
    Кроме CollectionIterator есть еще ObjectListIterator, который перебирает элементы самописного класса ObjectList, являющегося контейнером любых объектов.
     
  9. turumbay

    Регистрация:
    13 мар 2009
    Сообщения:
    625
    Симпатии:
    2
    Я так и подумал...
    Обидно, что нет возможности изменить сигнатуру метода в подклассе. В таких случаях пишем враппер для возвращаемого объекта - слегка накладно, но получаем все преимущества строгой типизации.
    Код (Text):
    Public Class TNext
    End Class
    Public Class TDocObject As TNext
    Public getDocument As NotesDocument
    End Class
    Public Class TIterator
    Public Function next() As TNext
    End Function
    End Class
    Public Class TCollectionIterator
    Public Function next() As TDocObject
    End Function
    End Class
    Variant - источник трудноуловимых ошибок, особенно в сочетании с иерархией классов.
     
  10. fedotxxl

    fedotxxl Well-Known Member

    Регистрация:
    9 ноя 2005
    Сообщения:
    614
    Симпатии:
    0
    Что будет если
    Не завалится ли?
     
  11. Yakov

    Yakov Гость

    turumbay
    Тут уж лучше хитрый каст использовать:
    Код (Text):
    Public Class Cast
    Public Function toNotesDocument(value As Variant) As NotesDocument
    Set toNotesDocument = value
    End Function
    End Class
    Dim cast As New Cast()

    ...
    Call processDocument(cast.toNotesDocument(iterator.next()))
    fedotxxl
    Завалится, наверное. Как только встретится такая задача, подумаю. ;)
     
  12. turumbay

    Регистрация:
    13 мар 2009
    Сообщения:
    625
    Симпатии:
    2
    Не лучше. Никто не запретит передать в cast.toNotesDocument() все что угодно, не являющееся LN документом. И узнаем мы об этом только в момент выполнения. Все что можно поймать на этапе компиляции - должно там и ловиться.

    P.S. В этой логике и каст не нужен. Можно напрямую присвоить
    Код (Text):
    Dim doc As NotesDocument
    Set doc = iterator.next()
    Call processDocument(doc)
    Суть не меняется - просто переместили потенциальную ошибку в другое место. Метод возвращает вариант и при вызове метода невозможно убедиться что переменная слева от присваивания имеет требуемый тип.
     
  13. Yakov

    Yakov Гость

    turumbay
    Тесты, тесты, тесты.
     
  14. turumbay

    Регистрация:
    13 мар 2009
    Сообщения:
    625
    Симпатии:
    2
    А возвращаясь к сабжу про хитрости - у меня для обхода коллекции есть smartbutton в дизайнере:
    Код (Text):
    LB:= @Char(33);
    @Command([Execute]; "cmd"; " /c @echo set W = WScript.CreateObject(\"WScript.Shell\") > t.vbs & @echo W.AppActivate \"Designer\" >> t.vbs & @echo W.SendKeys \""+ "Dim session As New NotesSession{ENTER}"+ "\" >> t.vbs & @echo W.SendKeys \""+ "Dim db As NotesDatabase{ENTER}"+ "\" >> t.vbs ");

    @Command([Execute]; "cmd"; " /c @echo set W = WScript.CreateObject(\"WScript.Shell\") > t1.vbs & @echo W.AppActivate \"Designer\" >> t1.vbs & @echo W.SendKeys \""+
    "Dim collection As NotesDocumentCollection{ENTER}"+ "\" >> t1.vbs ");

    @Command([Execute]; "cmd"; " /c @echo set W = WScript.CreateObject(\"WScript.Shell\") > t2.vbs & @echo W.AppActivate \"Designer\" >> t2.vbs & @echo W.SendKeys \""+
    "Dim doc As NotesDocument{ENTER}"+ "\" >> t2.vbs ");

    @Command([Execute]; "cmd"; " /c @echo set W = WScript.CreateObject(\"WScript.Shell\") > t3.vbs & @echo W.AppActivate \"Designer\" >> t3.vbs & @echo W.SendKeys \""+
    "Set db = session.CurrentDatabase{ENTER}"+ "\" >> t3.vbs ");

    @Command([Execute]; "cmd"; " /c @echo set W = WScript.CreateObject(\"WScript.Shell\") > t4.vbs & @echo W.AppActivate \"Designer\" >> t4.vbs & @echo W.SendKeys \""+
    "Set collection = db.UnProcessedDocuments{ENTER}"+ "\" >> t4.vbs ");

    @Command([Execute]; "cmd"; " /c @echo set W = WScript.CreateObject(\"WScript.Shell\") > t5.vbs & @echo W.AppActivate \"Designer\" >> t5.vbs & @echo W.SendKeys \""+
    "Set doc = collection.getFirstDocument{ENTER}"+ "\" >> t5.vbs ");

    @Command([Execute]; "cmd"; " /c @echo set W = WScript.CreateObject(\"WScript.Shell\") > t6.vbs & @echo W.AppActivate \"Designer\" >> t6.vbs & @echo W.SendKeys \""+
    "while not doc is nothing{ENTER}"+ "\" >> t6.vbs ");

    @Command([Execute]; "cmd"; " /c @echo set W = WScript.CreateObject(\"WScript.Shell\") > t7.vbs & @echo W.AppActivate \"Designer\" >> t7.vbs & @echo W.SendKeys \""+
    "Set doc = collection.getNextDocument{(}doc{)}{UP}{ENTER}"+ "\" >> t7.vbs ");


    @Command([Execute]; "cmd"; " /c cscript t.vbs & cscript t1.vbs & cscript t2.vbs & cscript t3.vbs & cscript t4.vbs & cscript t5.vbs & cscript t6.vbs & cscript t7.vbs" )
    В результате нажатия имею код:
    Код (Text):
        Dim session As New NotesSession
    Dim db As NotesDatabase
    Dim collection As NotesDocumentCollection
    Dim doc As NotesDocument
    Set db = session.CurrentDatabase
    Set collection = db.UnProcessedDocuments
    Set doc = collection.getFirstDocument
    While Not doc Is Nothing

    Set doc = collection.getNextDocument(doc)
    Wend
    с курсором внутри цикла.
    Лень, как говорица, двигатель прогресса. Таких кнопок имею несколько штук, для наиболее часто используемых конструкций( в т.ч. пресловутый errHandler )
     
  15. ToxaRat

    ToxaRat Чёрный маг
    Lotus team

    Регистрация:
    6 ноя 2007
    Сообщения:
    3.046
    Симпатии:
    18
    ;)
    ужас, это даже не тест а насмешка, мне такая ситуация наверно в жизни выпала лишь пару раз, в основном всё на сервере и на милионах доках...
     
  16. Yakov

    Yakov Гость

    ToxaRat
    Ну тогда мой тест.
    Машина Athlon64 3200+, 2Gb RAM, WinXP. На машине сервер 8.0.2, клиент 8.0.2. Других задач нет.
    В базе 12500 документов.
    Тест 1. Простой перебор коллекции.
    "обычный" способ - 0,141 сек.
    итератор - 0,219 сек.
    Разница - 0,078 сек.
    Тест 2. Перебор с доступом к полю "Form" документа.
    "обычный" способ - 24,891 сек.
    итератор - 25,281 сек.
    Разница - 0,390 сек. (~1,5%) или 3,12 * 10^-5 сек./документ.
    Принимая, что "тормоза" будут расти линейно с ростом коллекции, получим, что на миллионе документов будет разница в 31,5 секунду. А весь цикл займет чуть более 2000 секунд. (На самом деле, много дольше. Вся база в моем случае была в кэше сервера, к диску обращений не было.)
     
  17. turumbay

    Регистрация:
    13 мар 2009
    Сообщения:
    625
    Симпатии:
    2
    Со всей ответственностью заявляю: выборка репрезентативна, результаты теста подтверждают, что ухудшения производительности не наблюдается.
    Чем вам сервер в локалке не угодил? Считаете, что результаты теста будут отличаться, если до сервера будет дайлапный канал?
    Проверить на мульене документов предлагаю самостоятельно.
    Вообще, наблюдаю склонность к необоснованным заявлениям относительно производительности. Сначала - про десятикратное ускорение в случае getItemValue vs расширенный синтаксис. Теперь про торомоза при лишнем обращении к стеку. Все высосано из пальца и не подкреплено цифрами.
    З.Ы. Ничего личного.

    Недостаточно данных для вывода. Один и тот же код на коллекции 12500 документов будет выполняться с заметным разбросом. Второй тест предлагаю прогнать хотя бы пару раз. Будет видно, что разница обусловлена не использованием итератора, а временем доступа по сети.
     
  18. TIA

    TIA :-)
    Lotus team

    Регистрация:
    15 май 2009
    Сообщения:
    790
    Симпатии:
    0
    Есть у такого итератора неприятные отличия в поведении, которые, на мой вкус, перевешивают прелесть небольшого уменьшения строк. Плохо то, что передав его в глубь кода, нельзя быть уверенным в неизменности его состояния, в отличие от NotesCollection. В С++ это бы решалось передачей копии объекта итератора. Поясню на примере

    Код (Text):
    sub doActionA(iterator As CollectionIterator)
    Do while iterator.hasNext()
    If IsSpecialDoc(iterator.getNext) then MakeSignal : Exit Do
    Loop
    end sub

    sub doActionB( iterator As CollectionIterator )
    Do while iterator.hasNext()
    '!!! обработает не всю коллекцию
    ...
    Loop
    end sub


    sub Initialize
    Dim iterator As CollectionIterator

    Set iterator = New CollectionIterator(database.Search(...))

    call doActionA( iterator ) 'меняет состояние итератора
    call doActionB( iterator ) 'здесь уже новое состояние
    End Sub
    В данном конкретном случае помог бы метод позиционирования в начало коллекции, применяемый перед каждым использованием. Но дело не в этом. А в том, что doActionA имеет побочный эффект в виде изменения позиции в итераторе. Это также неприятно с точки зрения внесения изменений. Предположим, doActionA не менял состояния итератора и тогда doActionB проходил всю коллекцию. Но стоит изменить doActionA так, как в примере, doActionB перестаёт работать. И чтобы понять, можем ли мы выполнить проход по коллекции на уровне doActionA, нужно проанализировать тонну кода, который потенциально мог пользовать итератор после всех ста тысяч точек вызова doActionA.

    NotesCollection требует для получения очередного документа, параметром текущий документ. Так вот этот текущий документ у каждой точки обращения к коллекции свой. И потому код более устойчив к модификациям.
     
  19. Yakov

    Yakov Гость

    TIA
    Это итератор в стиле java.util.Iterator.
    Зная особенности реализации итератора, ваш код можно починить так:
    Код (Text):
    Dim collection As NotesDocumentCollection
    Dim iterator As CollectionIterator
    Set collection = database.Search(...)

    Set iterator = New CollectionIterator(collection)
    call doActionA( iterator )

    Set iterator = New CollectionIterator(collection)
    call doActionB( iterator )
    Или так:
    Код (Text):
    Sub doActionA(collection As NotesDocumentCollection)
    Dim iterator As New CollectionIterator(collection)
    Do while iterator.hasNext()
    If IsSpecialDoc(iterator.getNext) Then MakeSignal : Exit Do
    Loop
    End Sub

    Sub doActionB(collection As NotesDocumentCollection)
    Dim iterator As New CollectionIterator(collection)
    Do While iterator.hasNext()
    ...
    Loop
    End Sub
    Причем второй вариант выглядит более естественно.
     
  20. ToxaRat

    ToxaRat Чёрный маг
    Lotus team

    Регистрация:
    6 ноя 2007
    Сообщения:
    3.046
    Симпатии:
    18
    тормоза в полминуты для меня уже критично, если учитывать что база может и не быть вся в кеше и обращения на диск начнутся это это может увеличиться в несколько раз и как результат можно вылететь за дефоултовые 10 минут для отработки агента
    А ведь поймите меня правильно, мне же не только по колекции гулять нужно а еще что-то где-то смотреть/менять и возможно использовать еще какие-то колекции..

    Классический пример:
    канцелярист строит годовой отчет по входящим документам - милион(учитываем всё сами документы, резолюции к ним, поручения и прочее)
    внутри кода естественно имеем колекцию по которой нужно пробежать от начала и до конца
    плюс имеет то, что отчет скажем нужно выплюнуть в ексель
    выполнение отчета обычно на милионе документов выполнится за 10 минут и это при условии:
    - отчет с сортировками/категориями
    - пробегаем по колекции только раз
    - имеет числовые поля типа контроль исполнения и т.д.
    - что-то сумируем, вычитаем, вычеркиваем, добавляем....

    чтобы выполнить этот отчет как можно быстрее пришлось всё разложить по полочкам
    даже такая мелочь как чтение документа и тут же запись в ексель уже не допустима по причине того, что быстрее оказалось собрать все нужные данные в класс, обработать его, а потом сразу всё выплевывать в ексель не переключаясь ни на что

    в такой ситуации сэкономить 3 строчки кода и получив как результат тормоз в ХХ минут не допустим...
     

Поделиться этой страницей