|
|
|
May 14th, 2009
12:00 am - "Существует миллион способов чтобы жить" У нас на работе, у sleepy есть замечательная фраза. Когда у нас возникали проблемы с чужими библиотеками или просто мы вдруг осозновали, что дальше так программировать нельзя, он всегда говорил, что существует миллион способов чтобы жить. И действительно, способ жить всегда находился и работал.
Тем не менее, за то время пока я живу у меня сложилось свое мнение на то, какое должно быть программирование, и какой должен быть идеальный код. Оно состояло из моих личных наблюдений и не только. Ну например, я считал, что:
- Объекты которые имеют сложное состояние -- зло. Поддержка таких классов превращается в кошмар. Идеально, чтобы объекты не имели состояний.
- Ограничения вроде "я могу вызывать функцию .read(), только если .valid()" -- зло, поскольку он этого только плодяться лишнии ошибки. Желательно, чтобы все, до чего я могу дотянуться, все это я могу вызвать.
- Исключения -- хорошо. Они сокращают размер кода, избавляя от всякого мусора типа проверка кодов возврата.
- STL -- хорошо. Классы должны предоставлять минимальный набор функций. Всё что можно написать снаружи надо написать снаружи. Много мелких унифицированных компонентов (STL-ные итераторы и т.п.) из них можно сооружать любую структуру и они дают страшную реюзабельность кода.
Совсем недавно я наткнулся на QXmlStreamReader. Он противоречил всем моим заповедям. Но он был настолько чертовски удобным, что у меня язык не поворачивался назвать его говном. :-)
Это было просто ужасно. "Он не правильный" -- говорил я себе, но любая фича, которая мне была нужна, присутствовала в нём и её было удобно юзать!
А теперь обо всём по-порядку.
- QXmlStreamReader имеет сложное состояние. Оно соответствует тому какой токен он прочитал последним. При этом в случае ошибки он переходит в специальное состояние "ошибка". Даже больше, если он находиться в состоянии "ошибка" функция atEnd() будет возвращать true. Поэтому дефолтный цикл разбора XML у него выглядит так:
while (!r.atEnd())
{
QXmlStreamReader::TokenType tt = r.readNext();
// ...
}
if (r.hasError())
{
// ...
}
| | _Winnie C++ Colorizer |
"Это ужасно" -- говорил я себе, но это требует минимум писанины и это именно то, что мне было нужно, чтобы разобрать XML.
- В завимимости от того какой токен 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". Писать рекурсивный парсер оказалось действительно удобно! И самое удивительное, это то, что я ни разу не вызвал не ту функцию в не том состоянии.
- Qt не использует исключения. Это означает, что исключения не должны пролетать код Qt. Я бы в принципе мог бы их заюзать (и я поначалу так и сделал), но оказалось удобнее использовать функцию QXmlStreamReader::raiseError. Что это?
- На кой фиг QXmlStreamReader иметь функцию raiseError? Как она вообще к нему относить? А вот как. Функция raiseError переводит QXmlStreamReader в состояние "ошибка" и выставляет ему соответствующее error-message. Да, после этого мне остается только выйти из функции, а цикл вытягивания токенов сам обработает эту ошибку. Это оказалось не сложнее использования исключений. :-)
Да, это удобно. Да, написать код без ошибок легко. Да, он прост в изучении. Но всё в нём противоречит тому что я считал правильным. :-)
Сейчас я сижу и думаю какой бы был интерфейс у моего XML-парсера. На вход он бы получал пару итераторов (first, last) На выход он бы выдавал пару, итератор, куда он сдвинул first, и страшный boost::variant<> того, что он прочитал. А дальше бери и делай boost::apply_visitor(). В случае ошибки он бы бросал исключение.
Вот теперь сижу и думаю: "а в чём преимущество?".
UPD: Я, конечно, погорячился сделать XML-парсер просто функцией. Т.к. в этом случае он не сможет выполнять простейшую проверку корректности XML (то, что закрывающийся тег соответствует открывающемуся и т.п.), но это существует много способов это вылечить. Например, создать некоторый контекст описывающий состояние разбора и передавать его этой функции. Или просто после вызова этой функции и после получения токена вызывать ctx_.match(token), если необходима валидация. Этот контекст может даже иметь набор функций для его инспектирования и модификации, что даст интересные фичи, например, можно будет разбирать XML не с начала.
|
Comments:
1) заметь, здесь вообще не нужен класс — достаточно функции read_xml_token, типа
while i != e:
i, tok = read_xml_token(i, e)
handle(tok)
2) все было бы вообще круто, если бы не c++. variant'ы практически невозможно использовать (я пытался). 3) надеюсь, ты понимаешь, в чем же преимущество :-) 4) но с правдой жизни не поспоришь — есть много способов, чтобы жить! лучший код — это который работает.
> 1) заметь, здесь вообще не нужен класс — достаточно функции read_xml_token
Кажеться, именно такой интерфейс я и описал у своего парсера. ;-)
> 3) надеюсь, ты понимаешь, в чем же преимущество :-)
Оно чисто мнимое. Бывает примущество, когда реально, что-то улучшается, а бывает, когда это только в теории. Вот, реально, QXmlStreamReader работает? Работает. Всё что хочеться написать пишеться просто и компактно. Этого оказывает достаточно.
![[User Picture]](http://l-userpic.livejournal.com/90982690/10682242) | | From: | insooo |
| Date: | May 14th, 2009 03:46 am (UTC) |
|---|
| | | (Link) |
|
О да! Только меня постоянно уносит на темную сторону не из-за сложных и неортогональных конструкций а из-за плоских плейн-Си библиотек функций, которые можно _просто вызвать_ из любого места без необходимости создания объекта.
"сложных и неортогональных конструкций" -- это то что я пишу? :-)
"из-за плоских плейн-Си библиотек функций, которые можно _просто вызвать_ из любого места без необходимости создания объекта" -- я ничего не понял. Что ты имеешь ввиду?
А знаком с реализацией SAXParser в Java? Там, как раз, через коллбэки. Чтобы сделать действительно удобный парсинг, пришлось писать свой враппер. Но, тем не менее, XML парсится и шустро :).
Раньше я думал, что boost это клёво. Но со временем стал понимать, что некоторые его парадигмы заставляют писать просто очень сложный код, кучу классов и прочее. конечно, это типа супер-структурированность, но код становится практически нечитаем... не проще ли написать опасный код с несколькими проверками, чем городить частокол из компонентов boost и stl?... Нет, я не против boost, он клёвый.. просто программирование на C++ с каждым годом становится всё сложнее и сложнее... =)
boost -- это очень правильно. Но для такой писанины у нас язык не очень хорошо подходит. Лучше, чем другие языки, но всё же недостаточно хорошо. Поэтому получается код страшненький и весь в хаках, хотя идея в нём может быть заложена очень правильная. Вот только не каждый сможет эту идею за хаками разглядеть. |
|