Непричёсанные мысли по поводу формата сохранения: теория +8





Начнём с вводных. Мы разрабатываем программу, которая будет сохранять свои данные в файл, и при этом…


  • будет расширяться, и существенно (отпадают уровни и сохранения большинства игр: после пары патчей бросаем игру и пишем новую);
  • тем не менее программа не рассчитывает на то, чтобы быть стандартом (отпадает LibreOffice). То есть по формату сохранения она должна быть совместима только с собой-старой и собой-новой;
  • все её данные надо держать одномоментно в памяти; СУБД типа SQLite не даёт каких-то преимуществ (отпадают базы переписки в почте или мессенджере);
  • но файл сохранения будет очень велик (отпадают программы фотопроявки вроде Lightroom, где документ — это всего лишь положения сотни-другой ползунков: мелочь по сравнению с 40-мегабайтным RAW);
  • нет нужды в ручной корректировке файлов (отпадает пользовательский интерфейс типа «файл конфигурации», присущий, например, серверу Apache).

Таких программ на самом деле немало. Это AutoCAD, Photoshop, Microsoft Office (будем честными: даже пытаясь протащить его через ISO, «мелкомягкие» рассчитывали, что он будет совместим в первую очередь с самим собой).


И для простоты добавим ещё одно требование, которое отбросит все три этих программы, но довольно реалистичное (ему отвечают Windows 10 и куча программ помельче).


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

Я долго искал программу, которая отвечает всем этим требованиям, и наконец нашёл: Inkscape. Несмотря на то, что её файлы основаны на стандарте SVG, половина всей информации лежит в расширениях SVG — пространствах имён «sodipodi» и «inkscape».


В первой части постараюсь обойтись без программирования, зато объясню парочку важных концепций.


Собственно, у нас есть такие возможные форматы для хранения информации.


Формат первый. XML


Из форматов, доступных из коробки, самый распиаренный — XML. Если правильно писать код разбора, часть задач по обратной совместимости решаются автоматически: старая программа пропустит все лишние тэги. Остаётся сделать, чтобы новая читала документы, созданные старой — и дело в шляпе.


[+] Много стандартного инструментария.
[+] Оптимальный выбор, если мы храним размеченный текст.
[±] Можно (хотя и тяжело) редактировать файл вручную.
[?] Разбор может занимать много времени — от 20 до 95% всего времени загрузки. Эту неоптимальность даже обозвали «налогом на угловые скобки». По опыту загрузки (не полной) XLSX собственными силами: раззиповка — 0,3 с, разбор XML — 1,1 с, остальное — 0,6 с.
[?] Программировать своими силами очень опасно, возможны незаметные ошибки.
[?] Зачастую избыточен.
[?] Если в программе есть огромные массивы из малюсеньких объектов (звуковые волны, картинки, матрицы чисел), их приходится сохранять не-XML’ными мерами. Посмотрите, например, как хранятся кривые в SVG:


<polygon id="Star-1" stroke="#979797" stroke-width="3" fill="#F8E81C"
            points="99 154 40 185 51 119 4 73 69 64 99 3 128 64 194 73 147 119 158 185 " />

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


Каким образом? Я знаю четыре подхода к чтению XML: на callback’ах (SAX), на потоках (StAX), на сопрограммах и DOM. А также два подхода к записи XML: прямая запись тэг за тэгом и DOM.


Чтение на callback’ах даёт сложный и путаный код, напоминающий конечный автомат. DOM расходует памяти в разы больше, чем структура предметной области. Сопрограммы — сложное дело и есть не везде. Так что наш выбор — чтение на потоках, запись тэг за тэгом. В обоих случаях расходуется пренебрежимо мало памяти, так что автоматически выполняется моё обещание не грузить громадину.


На языках, где мы управляем памятью вручную (Си), у потокового парсера есть ещё одна полезная черта. Управление памятью действует как унитазный бачок — потихоньку заполняется, мгновенно сливается — потому в самых быстрых из них действует собственное управление памятью. К сожалению, не получается добиться столь впечатляющей скорости, как в DOM (прежде чем выйти из функции getThing, надо «подобрать концы»). Так что у меня лично получилось (на Си++) сделать потоковый парсер в 2,5 раза медленнее, чем у рекордсмена PugiXML. Но это уже замечательный результат: научился грузить огромный XLSX втрое быстрее, чем Excel.


Формат второй. XML-лайты


«XML-лайтами» я называю языки наподобие XML, в которых единицы смысла — это строки текстового файла (иногда также агрегаты из нескольких строк). Наиболее известный — YAML.


name: Mark McGwire
accomplishment: >
  Mark set a major league
  home run record in 1998.
stats: |
  65 Home Runs
  0.278 Batting Average

Много лет назад, не накопав хорошего XML-инструментария для Delphi, за полдня я придумал XML-лайт с тупым названием Multilevel. Правда, тогда по неопытности работал только с DOM-разбором.


{ MULTILEVEL
  Format=McJoy
  Noise=10
  { AXES
    { AXIS
      Index=2
      Name=Тормоз
      Type=Analog
      Neutral=65535
      Threshold=25
    }
    { AXIS
      Index=3
      Type=Analog
      Neutral=32895
      Threshold=25
    }
  }
}

[+] Достаточно быстры.
[+] Редактируются вручную.
[±] Думаю, можно подобрать подходящий формат для огромных массивов из малюсеньких объектов.
[±] Легко запрограммировать своими силами.
[?] Даже если формат известный, вроде YAML, велика вероятность, что найдутся только DOM-подобные механизмы.
[?] Малопригодны для размеченного текста (и то придётся специально продумывать подходящий формат).


Формат третий. Двоичные XML-подобные коды


XML — формат текстовый. Но ничего не стоит перевести его в двоичный вид. Вот наобум придуманный XML-подобный двоичный формат (порядок байтов для наглядности взят Motorola):


89 AB CD EF    ; заголовок
00 01          ; блок с кодом 1
00 00 00 03    ; длина блока (=3)
AA BB CC       ; данные
80 02          ; каталог с кодом 2 (бит 80 00 означает «подкаталог», поля длины нет)
    00 03                      ; блок с кодом 3
    00 00 00 08                ; длина блока (=8)
    AA BB CC DD EE FF 00 11    ; данные
00 00          ; конец подкаталога

На XML бы это выглядело так.


<data>
  <block opcode="1">AA BB CC</block>
  <dir opcode="2">
    <block opcode="3">AA BB CC DD EE FF 00 11</block>
  </dir>
</data>

Я не открываю Америку, формат BIFF из Microsoft Office существует лет тридцать. Компания облажалась в двух местах: 1) Формат не иерархический, что сильно мешает дробить блоки; 2) блок ограничен 64 килобайтами, что решается вторым, третьим и т.д. блоком CONTINUE.


Недавно для целей медиаконтейнера Matroska сделали язык EBML — тот же XML, но двоичный. Конкретно с ним не знаком.


[+] Крайне быстры.
[+] Тэги, скорее всего, будут достаточно коротки, чтобы размеченный текст отнял даже меньше места в файле, чем в памяти.
[±] Легко запрограммировать своими силами.
[±] Огромные массивы из малюсеньких объектов можно сохранять как есть, в двоичном виде.
[?] Ручному редактированию не подлежат, для отладки потребуется программа-дампер.


Прочие форматы (и их недостатки)


Эти форматы лучше даже не рассматривать.


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


Любые сериализации через рефлексию (встроенные в Java, Delphi, C#). Годятся только для задач, где большая совместимость не нужна (уровни и сохранения игр, файлы настроек). Если вам удастся задействовать рефлексию и сэкономить строки — вы молодец, но программа не должна полагаться только на неё.


UPD. Чем плоха рефлексия? Костылями при серьёзных изменениях структуры файла.


SQLite продвигает свой формат БД как формат документа. По-моему, из-за сложностей программирования с ним имеет смысл работать именно как с СУБД.


JSON несколько проще в программировании, чем XML, но всё равно сложен. Плюс затруднено потоковое считывание: JSON различает объекты и массивы, и синтаксис JS говорит, что от перестановки атрибутов результат не меняется.


{  // объект
   "address": {   // объект
       "streetAddress": "Московское ш., 101, кв.101",
       "city": "Ленинград",
       "postalCode": 101101
   },
   "phoneNumbers": [  // массив
       "812 123-1234",
       "916 123-4567"
   ],
   "firstName": "Иван",  // хотелось бы их иметь в начале
   "lastName": "Иванов"
}

Хотя JSON подсказывает нам одну важную идею. А именно…


Идея 1. Или последовательность, или коллекция


Нашу жизнь очень сильно упростят два важных требования.


Любой тэг должен быть или последовательностью тэгов/блоков, или коллекцией тэгов/блоков. Объясню, что это такое.


  • Последовательность — это когда каждый сын повторяется не более одного раза и знает своё место. При этом возможны необязательные или взаимоисключающие.
    • В терминах регулярных выражений — что угодно без дублей и операции «повторить». Например, AB [C] (D | [E]F) G.
    • Неизвестные тэги, замеченные в последовательности, пропускаются.
    • Пример: тэг <html> будет последовательностью из <head> и <body>.
  • Коллекция — это повтор тэгов из какого-то ограниченного набора, с предельно простыми правилами взаимодействия друг с другом. Что-то вроде (A|B|C)*.
    • Неизвестные тэги, замеченные в коллекции, пропускаются или вызывают ошибку по желанию программиста.
    • Пример: тэг <body> будет коллекцией из (почти) всех возможных тэгов HTML. Тэг <tr> содержит только <td> и <th>.

Примечание. Я понимаю, что в HTML оба этих тэга, td и th, можно использовать как непарные, браузер разберётся. Но давайте будем считать, что имеем дело с XHTML и эти тэги парные.


А вот совершенно не подходит под нашу модель тэг table. В официальной документации он описан как [caption], [title], [summary], ( col* | colgroup* ), (( [thead], [tfoot], tbody+ ) | ( tr+ )) — не похоже ни на коллекцию, ни на последовательность. Я понимаю разработчиков: HTML-то пишется в первую очередь руками. Но в машиночитаемом формате лучше так не делать.


Делая коллекцию дочкой коллекции, надо чётко осознавать риски. Тэг table в одном из вариантов будет коллекцией тэгов tr. А он, в свою очередь, коллекция из td и th. Если вдруг, случись что, в tr появится какой-нибудь тэг tx, с большой вероятностью оборвётся совместимость.


Как бы мы переписали нашу несчастную таблицу под машинное сохранение?


table :== [caption] [title] [summary] [cols|colgroups] [thead] [tfoot] (tbodies|tbody)
cols :== col+
colgroups :== colgroup+
tbodies :== tbody+
thead, tfoot, tbody :== tr+
tr :== (td|th)+

Но при этом мы чётко понимаем, что внутри коллекции tbody находится коллекция tr. Но таблица — она двухмерная, и крайне мала вероятность, что внутри tr вставят что-то не являющееся клеткой таблицы — так что закрываем на это глаза.


А вот в нашем простом иерархически-двоичном формате так нельзя. Причина в том, что у тэга XML есть атрибуты, куда, если что, можно закинуть немножко информации. А у каталога двоичного формата нет ничего — потому в этот самый tr не впишешь никаких новых данных: ни стиля, ни соответствия электронного документа и бумажного источника… Потому приходится полностью исключать коллекции в коллекциях.


thead, tfoot, tbody :== trs
trs :== tr+
tr :== tds
tds :== (td|th)+

Выносите отдельные сущности, особенно необязательные, в тэг-контейнер. Заголовок к таблице можно описать двумя способами:


<table caption="xxx" />
<table><caption>xxx</caption></table>

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


Впрочем, <table class="myTable" /> вполне себе катит: ссылка на стиль — свойство, заголовок, висящий где-то на мониторе — сущность.


Идея 2. Extend, upgrade, break. Или расширить, модернизировать, порвать


Какую бы мы ни придумали конструкцию — когда-нибудь мы из неё вырастем. Пусть у нас редактор простейших уровней, например, для Super Mario. Изначально там был один «мир» (скажем, лес). Делаем так, чтобы были два, три и больше миров. Как это будет выглядеть в XML?


БЫЛО:
<tileset />

СТАЛО:
<tilesets>
  <tileset />
  <tileset />
  <tileset />
</tilesets>

Новую программу можно научить открывать старый формат. Но старая программа не откроет нового. И тогда я предлагаю трёхстадийный «мягкий переход». Сколько длится каждая из стадий — зависит от того, насколько сложен формат, как вы заставляете пользователей обновлять программу и как вы готовы тестировать её на проектах старых версий.


(название намеренно сделано похожим на Microsoft’овское Embrace/Extend/Extinguish, да и суть та же).


Расширить: программа пишет старый формат, если в игре один мир, и в новый, если много.


Примерно через полгода-год происходит фаза Модернизировать: программа начинает писать только новый формат, всё ещё читая оба.


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


Идея 3. Работая с текстовыми файлами, используйте нейтральную локаль


Разрешите процитировать IT Happens:


«Странно», — подумал я, но девочка из ПФР оставила ман в сто страниц А4. Решив начать по-хорошему, я сел и стал его вдумчиво курить. Где-то на 40-й странице я наткнулся на указание сменить стандартный разделитель целой и дробной частей в винде на запятую, иначе программа не будет работать. Открыл, смотрю — ага, всё верно. Сменил на точку — программа ФОМС заработала, но перестала работать программа ПФР, выдавая всю ту же ошибку сохранения данных. Теперь пожилая женщина-кадровик обучена переключать разделитель, а я проникся небывалой нежностью к отечественным программистам.

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


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


<program name="MyProgram" savedWith="1.1.1.42" fullyCompatibleWith="1.1.1.40" partlyCompatibleWith="1.1.1.25" />

Цифру partlyCompatibleWith поднимаем до текущей, когда делаем extend или break в чём-то важном. Можно её устанавливать умно в зависимости от того, сохраняли мы в старом формате или нет. Цифру fullyCompatibleWith поднимаем, когда добавляем любой тэг.


Если файл загружен программой версии меньше 25, вывести: «Настоятельно рекомендуем обновить программу. Есть вероятность, что важные данные потеряны».


Если файл загружен программой версии меньше 40, вывести: «Рекомендуем обновить программу. Есть вероятность, что какие-то данные потеряны.»


Если файл загружен какой-то очень новой программой, чей partlyCompatibleWith больше 42, тоже можно что-то вывести.


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


Также в новых версиях программы можно хранить чёрный список версий, которые часто падают или портят файлы — если замечено, что savedWith в чёрном списке, тоже что-то вывести.

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

Писать практику по потоковому разбору XML-лайта?

  • 48,8%Да20
  • 51,2%Нет21

Вы можете помочь и перевести немного средств на развитие сайта



Комментарии (54):

  1. hssergey
    /#21122806 / +1

    все её данные надо держать одномоментно в памяти;

    А если данных будут десятки гигабайт? Или предполагается запуск на мощных серверах с сотнями гигов ОЗУ? В памяти надо держать то, что необходимо в данный момент.

    СУБД типа SQLite не даёт каких-то преимуществ (отпадают базы переписки в почте или мессенджере);

    Отпадание баз переписки — это проблема конкретных мессенжеров, кривости рук их разработчиков (вы про Skype?). Сама СУБД тут ни в чем не виновата…

    SQLite продвигает свой формат БД как формат документа. По-моему, из-за сложностей программирования с ним имеет смысл работать именно как с СУБД.

    Не очень понятно, что в этом плохого. Библиотеки для SQLite есть для многих языков программирования. Расширяемость — вообще не проблема — добавляй новые столбцы в таблицу или создавай новые таблицы. Иерархическая структура данных — в виде реляционной БД. Файл целиком грузить в память не нужно. Вручную разбирать его тоже не нужно, запросами можно получать данные, необходимые именно в этом конкретном месте. Аналогично с записью. Формат файла документирован, достаточно стороних инструментов для его просмотра и редактирования, если будет такая необходимость.

    • Mercury13
      /#21122814

      Совершенно верно, вы сказали тот случай, когда СУБД более чем оправдана: данных много, и нет нужды держать все в памяти. Потому и сказал: отпадают базы переписки. Но что, мало прог, где реально всё держим в памяти?

      Во-первых, в принципе работа с СУБД сложна, по опыту. Во-вторых, бывают и очень нелюбимы программистами изменения структуры файла — и на XML-то это противное дело, а на БД тем более.

      • GokenTanmay
        /#21122988 / +2

        Прелесть БД — бэкап из коробки, множественный доступ, согласованность, целостность.
        Если программа пишется в одно лицо, и только одно приложение будет шариться по файликам — то да, россыпь файлов ограниченна только фантазиями разработчика.
        Но данная лафа кончается, когда к данным необходимо ходить из нескольких модулей одновременно и появляется требование иметь хоть сколько нибудь не противоречивый бекап.

        в принципе работа с СУБД сложна
        Сарказм?

      • TargetSan
        /#21124532 / +1

        Исходя из п.4, вам оочень стоит рассмотреть SQLite как формат, убирающий большинство проблем с потерями. Поскольку при чтении-записи старые версии программы смогут работать со знакомыми им данными, не убивая старые при перезаписи части информации.

    • GRaAL
      /#21123398

      Вы, по-моему, что-то не так поняли. Пункты, на которые вы отвечаете, содержатся в разделе «Мы разрабатываем программу, которая...», т.е. это некоторые выставленные предусловия, для которых автор приводит решение. Одно из рассматриваемых предусловий — «все данные надо держать в памяти».
      Автор не говорит что «всегда надо данные держать в памяти, а SQLite не дает преимуществ», автор говорит «рассмотрим случай, когда данные надо держать в памяти, в этом случае SQLite не даст нам преимуществ».

    • Mercury13
      /#21123868

      Отпадают — имеется в виду «не рассматриваем», «это не наш вопрос».

  2. trir
    /#21122820 / +2
    • Mercury13
      /#21122832

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

      • Dotarev
        /#21122872 / +1

        Второе — ключевая фраза «Protocol Buffers — структура данных, которая затем компилируется в классы».

        • Mercury13
          /#21122888

          Всё равно это разновидность рефлексии при компиляции. А что, разве только при выполнении она бывает? Главный недостаток рефлексии — при сложных изменениях структуры будут костыли.

          • Dotarev
            /#21122894

            отражение или рефлексия. Вы именно это значение термина используете?

            • Mercury13
              /#21122902

              Да. Когда объект знает о своём внутреннем устройстве.

              • Dotarev
                /#21122914

                Тогда обратите внимание: «рефлексия… означает процесс… во время выполнения». Так что:

                только при выполнении она бывает?

                ответ: Да. И «сэкономил работы рефлексией. [Google]— молодец»

                • Mercury13
                  /#21122932

                  Не только при исполнении. При компиляции тоже возможна. Гугли «compile-time reflection».

                  • Dotarev
                    /#21122940

                    Но тогда Ваш ответ в 07:44 ложен. Увы, ухожу в оффлайн.

  3. hssergey
    /#21122836 / +1

    Но что, мало прог, где реально всё держим в памяти?

    Для небольших программ, например, программ редактирования документов, вполне оправдано держать все в памяти. Но попробуйте, например, в LibreOffice открыть документ на десятки мегабайт — уже почувствуете боль. А если объем данных заранее неизвестен и может быть очень большим — тут нужны иные подходы.

    Во-первых, в принципе работа с СУБД сложна, по опыту.

    Достаточно будет выучить самые базовые вещи SQL, этого уже хватит для работы. Более сложные конструкции могут и не понадобиться.

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

    Вот тут не согласен. В случае СУБД вообще нет никакой нужды лезть в структуру файла. Например, добавились новые колонки в таблицу. Один раз делаешь ALTER TABLE при миграции данных на новый формат (да, версионность нужна, куда без нее. Но даже версию можно хранить в БД в отдельной таблицу). Те места в коде, где эти колонки не используются, можно вообще не трогать, там все будет работать по-прежнему. Аналогично с добавлением новых таблиц. То есть ты в принципе не касаешься конкретной физической структуры файла, а работаешь непосредственно с данными.

    • Mercury13
      /#21122846

      Тут, получается, ALTER по любому чиху, даже если добавляем какой-то параметр.

      Сложность работы с СУБД не столько в сложности SQL, сколько в промежуточном слое между программной логикой и обёрткой над СУБД, который генерирует гарантированно корректный SQL и инкапсулирует стандартную логику (то ли ORM, то ли горбушка попроще).

      • atd
        /#21123114

        Если атрибутов много и часто меняется их набор, то в базе хранят их в виде obj_id + attr_key + attr_value, тогда alter не нужен.

        re ORM: если полноценный орм тащить не хочется, то между ним и ручным написанием запросов есть ещё куча готовых промежуточных решений.

        • ebt
          /#21127590

          Можете уточнить, каких?

      • doctorw
        /#21123632

        А мешает ли что-либо, для часто изменяющегося списка параметров, завести отдельную таблицу вроде Parameters, в которой делать insert/delete по необходимости?

  4. Dotarev
    /#21122868

    Deleted

    • Mercury13
      /#21122876

      Всё равно это разновидность рефлексии при компиляции. А что, разве только при выполнении она бывает?

      • doctorw
        /#21123642 / +1

        habr.com/en/post/343078/#comment_21122902

        В процессе компиляции разве есть экземпляры классов?
        И что плохого в так называемом «compile time reflection»?

        • Mercury13
          /#21123842

          В страшных костылях для обеспечения совместимости, как на этапе чтения, так и на этапе записи.

  5. Akon32
    /#21122948

    А вы не пробовали ini файлы? В Delphi их поддержка есть с незапамятных времён. А с некоторых пор даже json поддерживается стандартными классами.
    В чём плюс ini по сравнению с sqlite — сразу есть вменяемый (типа CRUD) API, и скорость, как ни странно, в разы выше. Минусы — нет транзакционности из коробки, неэффективно хранение бинарных данных.

    • leotsarev
      /#21123416

      Кстати есть отличный формат toml — фактически ini файлы, только стандартизированные.

      • Mercury13
        /#21125146

        Если наложить определённые ограничения на TOML, будет неплохой XML-лайт. «Просто так» TOML не годится для потокового считывания.

    • Mercury13
      /#21125130

      Ну, например, программа фотопроявки под названием DxO Photolab, известная своим высокотехнологичным шумодавом, работает на INI-файлах. Но сколько там данных у программы фотопроявки, повторяю… Самый большой файл, который нашёл у себя, около 50K. А для больших объёмов INI плохо пригодны.

  6. OldFisher
    /#21123120

    Кажется, автор попал в плен XML-дискурса. Ему подавай или XML, или XML-лайт, или бинарный XML. А ведь он изначально проектировался из соображений человекочитаемости (ну да, не получилось, но тем не менее).

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

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

    • leotsarev
      /#21123480 / +1

      Просто игнорировать незнакомые ключи может привести к отображению мусора для просмотрщиков и к испорченному файлу для редакторов. Нужны флаги как в PNG: public/private, safe to ignore, safe to copy.

      • OldFisher
        /#21123934

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

      • MonkAlex
        /#21123996

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

    • Mercury13
      /#21126560

      Я попал в плен этого дискурса, потому что выяснилось самое ценное преимущество XML — древовидность.

      • OldFisher
        /#21128480

        Все пудели — собаки, но не все собаки — пудели. То, что в XML есть древовидность, вовсе не означает, что для древовидного формата нужен именно XML или что-то ему родственное.

    • KvanTTT
      /#21127766

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

      Зачем свой? Куча бинарных форматов тоже существует, не надо изобретать велосипед. Например, MessagePack, в котором поддерживаются расширения, древовидные структуры. Он быстрый и компактный.

      • OldFisher
        /#21128488

        Тут по обстоятельствам. Иногда готовый формат превосходно подойдёт, а иногда лучше всё-таки создать свой, хорошо приспособленный к конкретным требованиям.

  7. APXEOLOG
    /#21123140

    JSON несколько проще в программировании, чем XML, но всё равно сложен. Плюс затруднено потоковое считывание: JSON различает объекты и массивы, и синтаксис JS говорит, что от перестановки атрибутов результат не меняется.

    Попробуйте YAML

    • TargetSan
      /#21124598

      Автор написал про YAML. И он кстати значительно сложнее.
      Сравните JSON grammar и YAML grammar. Формат описания разный, но общее представление о разнице в сложности, думаю, даёт.

  8. MonkAlex
    /#21123306 / +1

    В своё время использовал XML как простой вариант хранения настроек.
    Когда XML конфиг вырос до мегабайт, начал тормозить запуск приложения, я переехал в БД. Самый бюджетный вариант — SQLite. Запуск снова выполняется быстро, конвертировать БД намного проще (т.к. есть куча готовых вариантов в интернетах), дополнительно на уровне БД теперь есть ограничения — нельзя как в текстовые конфиги забить невалидные данные, даже ссылочная целостность уже дает много плюсов.

    Из вопросов к автору — что с сохрананием старых настроек в новый файл? Ну т.е. есть у меня приложение версии 10, есть приложение версии 20, и есть конфиг версии 15. Сделать чисто запуск версии 10 на конфиге версии 15 не так сложно, а вот чтобы ещё и работало сохранение настроек без даунгрейда до 10 версии — сложная работа, стоит ли, какие есть хорошие приемы? У меня чаще получается, что 10 перетирает конфиг на 10 версию, 20 обновляет до 20 (потому что он то знает как мигрировать).

    ПС: json читабельнее xml, не понял минусов json-а в статье.

  9. leotsarev
    /#21123456 / +2

    Ещё важное преимущество SQlite — файлы вообще говоря сложная штука. Нужно делать сложные трюки вроде копирования файла, записи изменений в временную копию, при сохранении нужно атомарно удалить старый файл и переименовать временный.
    Тем временем в SQLite можно просто сделать BEGIN TRAN, изменения делать сразу в БД, а при нажатии кнопки сохранить делать COMMIT.
    Если семантика реляционки не подходит, можно сделать либо Entity-Attribute-Value, либо каждый объект хранить в JSON / другом формате в строковом поле в БД, либо в бинарном в protobuf. Даже просто ради написанной за вас транзакционности я бы текстовые файлы не использовал.
    Текстовый формат нужен в случае, если файл смотрит/пишет человек.

  10. Beholder
    /#21123738

    Не рассмотрены S-expressions (и их возможные вариации).

  11. galaxy
    /#21123870 / +1

    Расширяемый и компактный формат? ASN.1?

    • MonkAlex
      /#21123896 / -1

      Ещё бы человекочитаемым был этот ASN.1 =)
      Инструментов почти нет, довольно неудобная, имхо, штука.

      • strib
        /#21127666

        ASN.1 XER — вполне читаем, прозрачно конвертируется в BER. Как нет инструментов? Куча стандартных либ. При том написанных для телекома, т.е. работающих, раз мы тут пишем всякое. Написал нотацию и вперед. Wireshark, громадное количество тулов.

        • MonkAlex
          /#21127682

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

  12. Sumor
    /#21125280 / +1

    Вы изобретаете что-то типа ASN.1

  13. vintage
    /#21125574 / +1

    Не могу не поделиться своим разбором форматов данных: https://youtu.be/vBqJWQzPB3Y?list=PLXyFFhv8ucKSC96WOd7Ju2HmEWTA3jPa5&t=5652

    • MonkAlex
      /#21126240

      В видео все так же вопросы заданные в статье актуальны. Формат вы предлагаете, но всё ещё нет ничего готового, нужно описать весь мир самому. Да и библиотек всё ещё нет.
      В такой ситуации, JSON всё ещё заметно лучше.

    • Mercury13
      /#21126524

      Вот такой придумали XML-лайт. Верно, причина та же.
      JSON придуман, кстати, по другой причине — если источнику данных доверяешь, на JS в две строчки разбирается.

  14. Fedcomp
    /#21126186 / +1

    в Rust'е вот такую штуку видел: github.com/ron-rs/ron

  15. Biga
    /#21126586 / +1

    Вместо SQLite можно взять NoSQL базу (ключ-значение), например LMDB или LevelDB. С ними не надо писать SQL запросы. Да и SQLite при желании можно превратить в key-value базу.
    У базы данных преимущество в том, что вам не нужно парсить и заново сериализовывать всю сохранку, если требуется поменять одно единственное поле.

  16. pankraty
    /#21127260

    Не освещен вопрос возможности / удобства использования в системах контроля версий — тут SQLite и прочие бинарные форматы жестко проигрывают человекочитаемым.

    • OldFisher
      /#21128538

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

      • pankraty
        /#21128568

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