вторник, 29 октября 2013 г.

Подводные грабли, или проблемы несовместимости

При анонсе Qt5 было сказано много громких слов о том, как легко будет тем, кто раньше пользовался Qt4, перейти на новую версию. Слова оказались не так чтоб совсем пустыми, но местами пустоватыми. Рассмотрю один из случаев, с которым имел дело на днях.

Вот как всё было. Некая система, состоящая из сервера и соединяющихся с ним клиентов, была в меру сил протестирована (работаю над ней один, к сожалению) и запущена в работу. Казалось бы, всё работало нормально. Но нет, показалось. Вдруг на машине заказчика в процессе регистрации в системе появилась ошибка, которой при тестировании не было. Как так? Да, у меня возник такой же вопрос. Клиент якобы передавал серверу "недействительные данные". Включаем дедукцию - ага, регистрация тестировалась на клиенте, собранном под Qt4, а в настоящее время используется клиент, собранный под Qt5. Сервер же собран под Qt4. Ну и в чём проблема? Пока не ясно.

Идем дальше. Ставим отладочные сообщения в места, где происходит передача и получение данных, и... данные вроде бы есть, но десериализуются в пустой QVariantMap, хотя должен быть словарь с несколькими парами ключ-значение. Вот оно что! Подозрение сразу падает на сериализацию QUuid, т.к. в Qt4 объекты этого класса "из коробки" не сериализуются, требуется зарегистрировать данный тип вручную. Создаем тестовый проект, пробуем сериализовать QUuid под Qt5, сохранить, прочитать под Qt4, и не можем. Вот вам и простота, и легкость, и беспроблемность, и ещё много столь же громких и столь же пустых слов.

Но всё же — отчего так происходит? Пять минут копания в исходниках Qt — и ответ готов. Qt сериализует типы, зарегистрированные "из коробки", с указанием только их идентификатора (беззнаковое целое, 4 байта), а пользовательские типы — с указанием ещё и их имени (идентификатор при этом равен QMetaType::User). Не трудно догадаться, что происходит при попытке под Qt4 десериализовать данные (не зарегистрированные "из коробки" в этой версии), сериализованные под Qt5: встречаем идентификатор (в случае с QUuid - 30), естественно, не находим его (QMetaType::User равен 1024 в Qt5 и 256 в Qt4, что в любом случае больше 30). Создаём пустой объект. Всё. Данные потеряны.

Что тут можно сказать? Плохо, очень плохо. Для такого большого, серьёзного проекта, как Qt, подобное, на мой взгляд, недопустимо. Тем более, что пути решения есть, и они элементарны. Можно было с самого начала всегда указывать вместо идентификатора название типа для всех типов данных. Самую малость медленнее, зато совершенно надёжно. Ну, не сделали с самого начала, не предусмотрели, — ладно. Второй выход — добавить (скажем, в Qt 4.8.0 или 4.9.0, буде его когда-нибудь выпустят) перегруженный метод qRegisterMetaType, который бы принимал помимо имени типа ещё и идентификатор, чтобы можно было вручную зарегистрировать тот же QUuid с идентификатором 30. Тогда, опять же, проблем совместимости удалось бы избежать. Согласен, не самое чистое решение, но это лучше, чем совсем ничего.

P.S.: Возможно, кто-то задастся вопросом: а что это такое автор хранит и передаёт в виде QUuid? Инвайт-коды, коды восстановления аккаунта и т.д. Почему QUuid? Потому что достаточно трудно подобрать, легко генерируется в одну строчку, сериализуется (тут должен быть нервный смех). Не уверен, что это хорошее решение, но для моего проекта оно вполне подходит.

Комментариев нет:

Отправить комментарий