Home
Ivan Sorokin's LJ - "Существует миллион способов чтобы жить"

> Recent Entries
> Archive
> Friends
> User Info

Advertisement

May 14th, 2009


Previous Entry Add to Memories Tell a Friend Next Entry
12:00 am - "Существует миллион способов чтобы жить"
У нас на работе, у sleepy есть замечательная фраза. Когда у нас возникали проблемы с чужими библиотеками или просто мы вдруг осозновали, что дальше так программировать нельзя, он всегда говорил, что существует миллион способов чтобы жить. И действительно, способ жить всегда находился и работал.

Тем не менее, за то время пока я живу у меня сложилось свое мнение на то, какое должно быть программирование, и какой должен быть идеальный код. Оно состояло из моих личных наблюдений и не только. Ну например, я считал, что:

  1. Объекты которые имеют сложное состояние -- зло. Поддержка таких классов превращается в кошмар. Идеально, чтобы объекты не имели состояний.

  2. Ограничения вроде "я могу вызывать функцию .read(), только если .valid()" -- зло, поскольку он этого только плодяться лишнии ошибки. Желательно, чтобы все, до чего я могу дотянуться, все это я могу вызвать.

  3. Исключения -- хорошо. Они сокращают размер кода, избавляя от всякого мусора типа проверка кодов возврата.

  4. STL -- хорошо. Классы должны предоставлять минимальный набор функций. Всё что можно написать снаружи надо написать снаружи. Много мелких унифицированных компонентов (STL-ные итераторы и т.п.) из них можно сооружать любую структуру и они дают страшную реюзабельность кода.


Совсем недавно я наткнулся на QXmlStreamReader. Он противоречил всем моим заповедям. Но он был настолько чертовски удобным, что у меня язык не поворачивался назвать его говном. :-)

Это было просто ужасно. "Он не правильный" -- говорил я себе, но любая фича, которая мне была нужна, присутствовала в нём и её было удобно юзать!

А теперь обо всём по-порядку.
  1. QXmlStreamReader имеет сложное состояние. Оно соответствует тому какой токен он прочитал последним. При этом в случае ошибки он переходит в специальное состояние "ошибка". Даже больше, если он находиться в состоянии "ошибка" функция atEnd() будет возвращать true. Поэтому дефолтный цикл разбора XML у него выглядит так:
        while (!r.atEnd())
        {
            QXmlStreamReader::TokenType tt = r.readNext();
            // ...
        }
    
        if (r.hasError())
        {
            // ...
        }
    
    _Winnie C++ Colorizer

    "Это ужасно" -- говорил я себе, но это требует минимум писанины и это именно то, что мне было нужно, чтобы разобрать XML.


  2. В завимимости от того какой токен QXmlStreamReader прочитал последним у него можно вызывать разные функции. Например, если это StartDocument, то я могу вызвать у него documentVersion() и documentEncoding(), если это Characters, то text() и isWhitespace(), если это StartElement, то name() и attributes() и т.д.
    "Как так можно? Почему не передавать ему колбек?" -- зато "The big advantage of this pulling approach is the possibility to build recursive descent parsers with it". Писать рекурсивный парсер оказалось действительно удобно! И самое удивительное, это то, что я ни разу не вызвал не ту функцию в не том состоянии.


  3. Qt не использует исключения. Это означает, что исключения не должны пролетать код Qt. Я бы в принципе мог бы их заюзать (и я поначалу так и сделал), но оказалось удобнее использовать функцию QXmlStreamReader::raiseError. Что это?


  4. На кой фиг QXmlStreamReader иметь функцию raiseError? Как она вообще к нему относить? А вот как. Функция raiseError переводит QXmlStreamReader в состояние "ошибка" и выставляет ему соответствующее error-message. Да, после этого мне остается только выйти из функции, а цикл вытягивания токенов сам обработает эту ошибку. Это оказалось не сложнее использования исключений. :-)


Да, это удобно. Да, написать код без ошибок легко. Да, он прост в изучении. Но всё в нём противоречит тому что я считал правильным. :-)

Сейчас я сижу и думаю какой бы был интерфейс у моего XML-парсера.
На вход он бы получал пару итераторов (first, last)
На выход он бы выдавал пару, итератор, куда он сдвинул first, и страшный boost::variant<> того, что он прочитал. А дальше бери и делай boost::apply_visitor().
В случае ошибки он бы бросал исключение.

Вот теперь сижу и думаю: "а в чём преимущество?".

UPD: Я, конечно, погорячился сделать XML-парсер просто функцией. Т.к. в этом случае он не сможет выполнять простейшую проверку корректности XML (то, что закрывающийся тег соответствует открывающемуся и т.п.), но это существует много способов это вылечить. Например, создать некоторый контекст описывающий состояние разбора и передавать его этой функции. Или просто после вызова этой функции и после получения токена вызывать ctx_.match(token), если необходима валидация. Этот контекст может даже иметь набор функций для его инспектирования и модификации, что даст интересные фичи, например, можно будет разбирать XML не с начала.

(Leave a comment)

Comments:


From:[info]pgregory.pip.verisignlabs.com
Date:May 13th, 2009 10:49 pm (UTC)
(Link)
1) заметь, здесь вообще не нужен класс — достаточно функции read_xml_token, типа

    while i != e:
        i, tok = read_xml_token(i, e)
        handle(tok)


2) все было бы вообще круто, если бы не c++. variant'ы практически невозможно использовать (я пытался).

3) надеюсь, ты понимаешь, в чем же преимущество :-)

4) но с правдой жизни не поспоришь — есть много способов, чтобы жить! лучший код — это который работает.
[User Picture]
From:[info]ivansorokin
Date:May 13th, 2009 11:09 pm (UTC)
(Link)
> 1) заметь, здесь вообще не нужен класс — достаточно функции read_xml_token

Кажеться, именно такой интерфейс я и описал у своего парсера. ;-)

> 3) надеюсь, ты понимаешь, в чем же преимущество :-)

Оно чисто мнимое. Бывает примущество, когда реально, что-то улучшается, а бывает, когда это только в теории. Вот, реально, QXmlStreamReader работает? Работает. Всё что хочеться написать пишеться просто и компактно. Этого оказывает достаточно.
[User Picture]
From:[info]insooo
Date:May 14th, 2009 03:46 am (UTC)
(Link)
О да! Только меня постоянно уносит на темную сторону не из-за сложных и неортогональных конструкций а из-за плоских плейн-Си библиотек функций, которые можно _просто вызвать_ из любого места без необходимости создания объекта.
[User Picture]
From:[info]ivansorokin
Date:May 14th, 2009 07:37 am (UTC)
(Link)
"сложных и неортогональных конструкций" -- это то что я пишу? :-)

"из-за плоских плейн-Си библиотек функций, которые можно _просто вызвать_ из любого места без необходимости создания объекта" -- я ничего не понял. Что ты имеешь ввиду?
[User Picture]
From:[info]sadko4u
Date:May 14th, 2009 09:07 am (UTC)
(Link)
А знаком с реализацией SAXParser в Java? Там, как раз, через коллбэки. Чтобы сделать действительно удобный парсинг, пришлось писать свой враппер. Но, тем не менее, XML парсится и шустро :).
[User Picture]
From:[info]makcnmka
Date:May 14th, 2009 12:35 pm (UTC)
(Link)
Раньше я думал, что boost это клёво. Но со временем стал понимать, что некоторые его парадигмы заставляют писать просто очень сложный код, кучу классов и прочее. конечно, это типа супер-структурированность, но код становится практически нечитаем... не проще ли написать опасный код с несколькими проверками, чем городить частокол из компонентов boost и stl?... Нет, я не против boost, он клёвый.. просто программирование на C++ с каждым годом становится всё сложнее и сложнее... =)
[User Picture]
From:[info]ivansorokin
Date:May 14th, 2009 06:22 pm (UTC)
(Link)
boost -- это очень правильно. Но для такой писанины у нас язык не очень хорошо подходит. Лучше, чем другие языки, но всё же недостаточно хорошо. Поэтому получается код страшненький и весь в хаках, хотя идея в нём может быть заложена очень правильная. Вот только не каждый сможет эту идею за хаками разглядеть.

> Go to Top
LiveJournal.com