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

Я едва ступаю из своего VIM в эти дни и на тех приложениях, где я нахожу меня вынужденным в IDE, я быстро помню почему. Я стал настолько приученным к горячим клавишам VIM для выбора, замены, сворачивания, и т.д. что в моей производительности настилают пол.

Как другие, я редко работаю на скомпилированных языках больше, таким образом, у меня есть ярлыки установки, которые работают, мои Perl/PHP вставляют интерпретаторы для нахождения синтаксических ошибок. В некоторых случаях я даже настроил VIM для выполнения доступных модульных тестов через тот же интерфейс. Это также очень легко к alt-tab назад к браузеру (предпочтительно на другом мониторе) и хит "Управление-R" для обновления, всех, не касаясь мыши.

10
задан Jonathan Leffler 3 November 2009 в 04:17
поделиться

9 ответов

У меня есть код, в котором более длинная строка при необходимости складывается из двух более коротких сегментов. Фу. Вот мой опыт после 12 лет сохранения совместимости некоторых данных:

Определите свои цели - их две:

  • новые версии должны иметь возможность читать, что пишут старые версии
  • старые версии должны иметь возможность читать какие новые версии пишут (сложнее)

Добавить поддержку версии в выпуск 0 - По крайней мере, напишите заголовок версии. Вместе с сохранением (потенциально большого количества) старого кода считывателя, который может примитивно решить первый случай. Если вы не хотите реализовывать вариант 2, начните отклонять новые данные прямо сейчас !

Если вам нужен только случай 1 и ожидаемые изменения со временем довольно незначительны, вы готовы. Тем не мение, эти две вещи, выполненные до первого выпуска, могут впоследствии избавить вас от многих головных болей.

Преобразование во время сериализации - во время выполнения сохраняйте в памяти только данные в «новом формате». Выполняйте необходимые преобразования и тесты на пределе сохраняемости (конвертируйте в новейшие при чтении, реализуйте обратную совместимость при записи). Это изолирует проблемы версий в одном месте, помогая избежать труднодоступных ошибок.

Храните набор тестовых данных из всех версий.

Сохраняйте подмножество доступных типов - ограничьте фактически сериализованные данные несколькими типами данных, такими как int, string, double. В большинстве случаев дополнительный размер памяти формируется за счет уменьшенного размера кода, поддерживающего изменения в этих типах. (Впрочем, это не всегда компромисс, на который можно пойти со встроенной системой.)

например, не надо ' t хранить целые числа короче собственной ширины. (вам может понадобиться это сделать, когда вам нужно хранить длинные целочисленные массивы).

добавить прерыватель - сохранить некоторый ключ, который позволяет намеренно заставить старый код отображать сообщение об ошибке, что эти новые данные несовместимы. Вы можете использовать строку, которая является частью сообщения об ошибке - тогда ваша старая версия может отображать сообщение об ошибке, о котором она не знает - «вы можете импортировать эти данные с помощью инструмента ConvertX с нашего веб-сайта» не очень хорошо для локализованного приложение, но все же лучше, чем "Ungültiges Format" .

Не сериализуйте структуры напрямую - это логическое / физическое разделение. Мы работаем со смесью двух, у каждой из которых есть свои плюсы и минусы. Ни один из них не может быть реализован без дополнительных затрат времени выполнения, что может в значительной степени ограничить ваш выбор во встроенной среде. В любом случае, не используйте фиксированные длины массивов / строк во время сохранения, это уже должно решить половину ваших проблем.

(A) правильный механизм сериализации - мы используем сериализатор bianry, который позволяет запускать " chunk "при сохранении, который имеет заголовок собственной длины. При чтении дополнительные данные пропускаются, а отсутствующие данные инициализируются по умолчанию (что упрощает реализацию "чтения старых данных" в коде сериализации). Куски могут быть вложенными. Это все, что вам нужно с физической стороны, но для обычных задач требуется немного приукрашивания.

(B) используйте другое представление в памяти - воспроизведение в памяти может быть в основном картой < id, запись> , где id скорее всего будет целым числом, и запись может быть

  • пустой (не сохраненной)
  • примитивным типом (строковый, целочисленный, двойной - чем меньше вы используете, тем проще)
  • массив примитивных типов
  • и массив записей

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

. Запрос несуществующего значения по умолчанию возвращает инициализированное значение по умолчанию / ноль. когда вы помните об этом при доступе к данным и при добавлении новых данных, это очень помогает: представьте, что версия 1 будет вычислять «длину foo» автоматически, тогда как в версии 2 пользователь может переопределить этот параметр. Нулевое значение - в поле «тип расчета» или «длина» должно означать « )

  • настройка делится на две настройки (например, «добавить границу» можно разделить на «добавить границу в четные дни» / «добавить границу в нечетные дни».)
  • настройка добавляется, переопределяя (или хуже , расширение) существующей настройки.
  • Для реализации случая 2 вам также необходимо учитывать:

    • никакое значение не может быть изменено или заменено другим. (Но в новом формате он мог бы сказать «не поддерживается» и добавить новый элемент)
    • перечисление может содержать неизвестные значения, другие изменения допустимого диапазона

    фух. это было много. Но это не так сложно, как кажется.

    )
  • настройка делится на две настройки (например, «добавить границу» можно разделить на «добавить границу в четные дни» / «добавить границу в нечетные дни».)
  • настройка добавляется, переопределяя (или хуже , расширение) существующей настройки.
  • Для реализации случая 2 вам также необходимо учитывать:

    • никакое значение не может быть изменено или заменено другим. (Но в новом формате он мог бы сказать «не поддерживается» и добавить новый элемент)
    • перечисление может содержать неизвестные значения, другие изменения допустимого диапазона

    уф. это было много. Но это не так сложно, как кажется.

    вы также должны учитывать:

    • никакое значение не может быть изменено или заменено другим. (Но в новом формате он мог бы сказать «не поддерживается» и добавить новый элемент)
    • перечисление может содержать неизвестные значения, другие изменения допустимого диапазона

    фух. это было много. Но это не так сложно, как кажется.

    вы также должны учитывать:

    • никакое значение не может быть изменено или заменено другим. (Но в новом формате он мог бы сказать «не поддерживается» и добавить новый элемент)
    • перечисление может содержать неизвестные значения, другие изменения допустимого диапазона

    уф. это было много. Но это не так сложно, как кажется.

    11
    ответ дан 3 December 2019 в 16:29
    поделиться

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

    struct address_book
    {
      unsigned int length; // total length of this struct in bytes
      char items[0];
    }
    

    где 'items' - это массив переменной длины структуры, который описывает свой собственный размер и тип

    struct item
    {
      unsigned int size; // how long data[] is
      unsigned int id;   // first name, phone number, picture, ...
      unsigned int type; // string, integer, jpeg, ...
      char data[0];
    }
    

    В вашем коде вы повторяете эти элементы (address_book-> length сообщит вам когда дойдете до конца) с умным кастингом. Если вы нажмете на элемент, идентификатор которого вы не знаете или тип которого вы не знаете, как обрабатывать, вы просто пропускаете его, перепрыгивая через эти данные (от элемента-> размер) и переходите к следующему. Таким образом, если кто-то изобретет новое поле данных в следующей версии или удалит его, ваш код сможет с этим справиться. Ваш код должен уметь обрабатывать разумные преобразования (если идентификатор сотрудника изменился с целого на строку,

    2
    ответ дан 3 December 2019 в 16:29
    поделиться

    Люди, использующие реляционные базы данных, используют огромную концепцию.

    Это называется разбиение архитектуры на «логический» и «физический» уровни.

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

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

    Вам не нужно заново изобретать SQL, чтобы добиться этого.

    Если ваши данные находятся полностью в памяти, подумайте об этом. Отделите представление физического файла от представления в памяти. Запишите данные в каком-нибудь «универсальном», гибком, удобном для анализа формате (например, JSON или YAML). Это позволяет вам читать в общем формате и создавать структуры в памяти, сильно зависящие от версии.

    Если ваши данные синхронизируются в файловой системе, у вас есть больше работы. Опять же, посмотрите на идею дизайна СУБД.

    Не кодируйте простую безмозглую структуру . Создайте «запись», которая сопоставляет имена полей со значениями полей. Это связанный список пар имя-значение. Это легко расширяется, чтобы добавить новые поля или изменить тип данных значения.

    Если вам больше не нужно его использовать, это одно, но для того, чтобы иметь возможность работать с кодом, который использует старую версию структуры, не удаляйте поле.
  • может быть разумным включить поле номера версии, но часто для этой цели можно использовать поле count.
  • Вот пример - у меня есть загрузчик, который ищет структуру с фиксированным смещением в образе программы для получения информации об этом образе, который мог быть прошит в устройство.

    Загрузчик был переработан, и он поддерживает дополнительные элементы в структуре для некоторых улучшений. Однако старый образ программы может быть прошит, и этот старый образ использует старый формат структуры. Поскольку вышеперечисленные правила соблюдались с самого начала, новый загрузчик полностью справится с этим. Это легкая часть.

    И если структура будет изменена дальше, и новый образ будет использовать новый формат структуры на устройстве со старым загрузчиком, этот загрузчик тоже сможет справиться с этим - он просто ничего не сделает с улучшениями. Но поскольку никакие поля не были (или будут) удалены, старый загрузчик сможет делать то, для чего он был разработан, и делать это с новым образом, имеющим структуру конфигурации с новой информацией.

    если говорить о реальной базе данных, в которой есть метаданные о полях и т. д., то эти рекомендации на самом деле не применимы.

    3
    ответ дан 3 December 2019 в 16:29
    поделиться

    I have handled this in the past, in systems with very limited resources, by doing the translation on the PC as a part of the s/w upgrade process. Can you extract the old values, translate to the new values and then update the in-place db?

    For a simplified embedded db I usually don't reference any structs directly, but do put a very light weight API around any parameters. This does allow for you to change the physical structure below the API without impacting the higher level application.

    2
    ответ дан 3 December 2019 в 16:29
    поделиться

    В последнее время я использую кодированный данные. Это формат, который использует BitTorrent. Просто, вы можете легко проверить его визуально, поэтому его легче отлаживать, чем двоичные данные, и он тесно упакован. Я позаимствовал код из высококачественной библиотеки C ++ libtorrent . Для вашей проблемы это так просто, как проверить, существует ли поле, когда вы их прочитаете. А для файла, сжатого с помощью gzip, это так просто:

    ogzstream os(meta_path_new.c_str(), ios_base::out | ios_base::trunc);
    Bencode map(Bencode::TYPE_MAP);
    map.insert_key("url", url.get());
    map.insert_key("http", http_code);
    os << map;
    os.close();
    

    Чтобы прочитать его:

    igzstream is(metaf, ios_base::in | ios_base::binary);
    is.exceptions(ios::eofbit | ios::failbit | ios::badbit);
    try {
       torrent::Bencode b;
       is >> b;
       if( b.has_key("url") )
          d->url = b["url"].as_string();
    } catch(...) {
    }
    

    Я использовал формат Sun XDR в прошлом, но сейчас предпочитаю его. Также его намного легче читать на других языках, таких как perl, python и т. Д.

    1
    ответ дан 3 December 2019 в 16:29
    поделиться

    Вставьте номер версии в структуру или сделайте так, как это делает Win32, и используйте параметр размера.
    если переданная структура не является последней версией, исправьте структуру.

    Около 10 лет назад я написал систему, аналогичную описанной выше, для системы сохранения компьютерной игры. Я фактически сохранил данные класса в отдельном файле описания класса, и если я обнаружил несоответствие номера версии, я смогу просмотреть файл описания класса, найти класс, а затем обновить двоичный класс на основе описания. Это, очевидно, требовало, чтобы значения по умолчанию заполнялись в новых записях членов класса. Он работал очень хорошо, и его также можно было использовать для автоматического создания файлов .h и .cpp.

    1
    ответ дан 3 December 2019 в 16:29
    поделиться

    Вы можете посмотреть, как библиотека Boost Serialization решает эту проблему.

    0
    ответ дан 3 December 2019 в 16:29
    поделиться

    Люди, использующие реляционные базы данных, используют огромную концепцию.

    Это называется разбиением архитектуры на «логический» и «физический» уровни.

    Ваши структуры являются одновременно логическими и физический уровень, смешанный воедино, в вещь, которую трудно изменить.

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

    Для этого не нужно изобретать SQL заново.

    Если ваши данные полностью хранятся в памяти, подумайте об этом. Отделите представление физического файла от представления в памяти. Запишите данные в каком-нибудь «универсальном», гибком, удобном для анализа формате (например, JSON или YAML). Это позволяет вам читать в общем формате и строить в памяти структуры, сильно зависящие от версии.

    Если ваши данные синхронизируются с файловой системой, у вас есть больше работы. Опять же, посмотрите на идею дизайна СУБД.

    Не кодируйте простую безмозглую структуру . Создайте «запись», которая сопоставляет имена полей со значениями полей. Это связанный список пар имя-значение. Это легко расширяется для добавления новых полей или изменения типа данных значения.

    Это позволяет вам читать в общем формате и строить в памяти структуры, сильно зависящие от версии.

    Если ваши данные синхронизируются с файловой системой, у вас есть больше работы. Опять же, посмотрите на идею дизайна СУБД.

    Не кодируйте простую безмозглую структуру . Создайте «запись», которая сопоставляет имена полей со значениями полей. Это связанный список пар имя-значение. Его легко расширить, добавив новые поля или изменив тип данных значения.

    Это позволяет вам читать в общем формате и строить в памяти структуры, сильно зависящие от версии.

    Если ваши данные синхронизируются с файловой системой, у вас есть больше работы. Опять же, посмотрите на идею дизайна СУБД.

    Не кодируйте простую безмозглую структуру . Создайте «запись», которая сопоставляет имена полей со значениями полей. Это связанный список пар имя-значение. Его легко расширить, добавив новые поля или изменив тип данных значения.

    4
    ответ дан 3 December 2019 в 16:29
    поделиться

    Я согласен с С.Лоттом в том, что лучшее решение - разделить физический и логический уровни того, что вы пытаетесь сделать. По сути, вы объединяете свой интерфейс и свою реализацию в один объект / структуру, и при этом вы упускаете часть возможностей абстракции.

    Однако, если вы должны использовать для этого одну структуру , есть несколько вещей, которые вы можете сделать, чтобы упростить задачу:

    1) Практически требуется какое-то поле с номером версии. Если ваша структура меняется, вам понадобится простой способ взглянуть на нее и уметь интерпретировать ее. В том же духе иногда полезно хранить общую длину структуры где-нибудь в поле структуры.

    2) Если вы хотите сохранить обратную совместимость, Вы должны помнить, что код будет внутренне ссылаться на поля структуры как на смещения от базового адреса структуры (от «передней части» структуры). Если вы хотите избежать нарушения старого кода, обязательно добавьте все новые поля в обратно структуры и оставьте все существующие поля без изменений (даже если вы их не используете). Таким образом, старый код сможет получить доступ к структуре (но не будет обращать внимания на дополнительные данные в конце), а новый код будет иметь доступ ко всем данным.

    3) Поскольку ваша структура может быть изменяя размеры, не полагайтесь на sizeof (struct myStruct) , чтобы всегда возвращать точные результаты. Если вы последуете пункту 2 выше, то увидите, что должны предполагать, что структура может вырасти больше в будущем. Вызовы sizeof () рассчитываются один раз (во время компиляции). Использование поля «длина структуры» позволяет вам быть уверенным, что, когда вы (например) memcpy структуру, вы копируете всю структуру, включая любые дополнительные поля в конце, о которых вы не знаете.

    4) Никогда не удаляйте и не сжимайте поля; если они вам не нужны, оставьте их пустыми. Не меняйте размер существующего поля; если вам нужно больше места, создайте новое поле как «длинную версию» старого поля. Это может привести к проблемам с дублированием данных, поэтому тщательно продумайте структуру и постарайтесь спланировать поля так, чтобы они были достаточно большими, чтобы обеспечить их рост.

    5) Не храните строки в struct, если вы не знаете, что можно ограничить их некоторой фиксированной длиной. Вместо, сохранить только указатель или индекс массива и создать объект хранения строк для хранения строковых данных переменной длины. Это также помогает защитить от переполнения строкового буфера, перезаписывающего остальные данные вашей структуры.

    Несколько встроенных проектов, над которыми я работал, использовали этот метод для изменения структур без нарушения обратной / прямой совместимости. Это работает, но это далеко не самый эффективный метод. Вскоре вы будете тратить пространство на устаревшие / заброшенные поля структуры, дублирующиеся данные, данные, которые хранятся по частям (первое слово здесь, второе слово там) и т. Д. И т. Д. Если вы вынуждены работать в рамках существующей структуры, это может работать на вас. Однако,

    1
    ответ дан 3 December 2019 в 16:29
    поделиться