Задать вопрос
@sirQaziop

Как реализовать обратную совместимость пользовательских данных в программе?

Разрабатывается десктоп-приложение на c#, в котором пользователь сможет создавать и вести собственный проект данных. Разработка итерационная, с каждой новой версией появляется и новая функциональность. Соответственно, в проекте растет количество данных, формат сохраняемого проекта также меняется (точнее, дополняется).
Требуется реализовать поддержку проектов старых версий программы, в частности:
1) Загрузку всех предыдущих форматов пользовательского проекта.
2) Сохранение текущего проекта в проект старого формата.
Собственно, вопрос: как лучше реализовать обратную совместимость форматов, где можно об этом почитать и существуют ли готовые решения?
  • Вопрос задан
  • 172 просмотра
Подписаться 1 Оценить Комментировать
Ответ пользователя Mercury13 К ответам на вопрос (4)
@Mercury13
Программист на «си с крестами» и не только
Проще всего это сделать в XML. Тогда мы легко можем добавлять новые теги и игнорировать то, что в XML не входит.

Двоичный файл надо делать на манер XML — в виде иерархической конструкции из коротеньких потоков. Самое сложное — чтобы по этому двоичному файлу не надо было бегать туда-сюда, как по гоночному треку. Для этого на каждом из уровней иерархии бывает одно из двух.
1. Запросить некую последовательность кодов, а затем спрашивать: есть этот код? А есть этот? Что-то типа (для простоты пишу как на C++)…
BlockReader blk;
int order1[] = { opHeader, opSettings, dirData };
BlockOrder order(blk, order1);

order.require(opHeader);
// считать заголовок
author = blk.readString();

if (order.get(opSettings) {
  // считать настройки
}

order.require(dirData);
blk.enterDir();
  // считать данные таким же образом — там может быть свой BlockOrder
blk.leaveDir();


Да, для чего мы запрашиваем порядок блоков дважды?
Первый раз — вот для чего. Представим себе устаревший файл, в котором нет блока настроек. Мы считываем заголовок, видим вместо блока настроек каталог с данными, и сразу вопрос: тут что-то пропущено или что-то лишнее?
Второй раз — для наглядности (код комментирует сам себя) и защиты от ошибок, когда заявленный в начале порядок не совпадает с реальным.

2. Просто считываем блоки по одному и интерпретируем. Обычно это бывает во всяких коллекциях.
while (blk.getBlock()) {
  switch (blk.opcode) {
  case dirTiledLevel:
     // считать плиточный уровень
  case dirGraphicLevel:
     // считать уровень с фоном — цельной картинкой
  }
}

Запрещается смешивать упорядоченное с коллекциями.

Можно также в заголовке каждого блока сделать бит: Essential. Старая версия, наткнувшись на такой блок и не считав ни байта (или наткнувшись на каталог и не войдя), выводит ошибку: версия явно новее, считать невозможно. Это бывает важно, когда в файле есть перекрёстные ссылки.

Для записи подобных файлов приходится накапливать блок в памяти, а затем сваливать его в один присест (ведь надо записать длину блока). Впрочем, есть и версия для структур, которые заранее знают свою длину — тогда накапливать в памяти излишне.

Далее. В заголовке можно сохранить поля «SavedWithVersion», «MinRequiredVersion», «MinFullySupportingVersion».
Добавляем новый блок (или новое поле в имеющийся блок) — поднимаем MinFullySupportingVersion до текущей. Изменяем структуру так, что ломаем совместимость — поднимаем MinRequiredVersion.

То, что версия слегка устарела, можно ловить и по косвенным признакам — какой-то блок считан не полностью, в какой-то каталог не вошли. У блока/каталога может быть флаг Compatibility — их проверять не надо. И наоборот — если важный блок отмечен как Compatibility, тоже версия устарела. Ясное дело, флаги Essential и Compatibility не могут попадаться вместе.

Как это делать, если формат сохранения — база данных (например, SQLite), я не в курсе.

А вот сохранение в устаревший формат, когда структуры данных ушли далеко вперёд, придётся налаживать руками. Можно ли в принципе, как преобразовать в старый формат, и т.д.
Ответ написан
Комментировать