Среда обитания Cynefin

Модель Cynefin (картинка для привлечения внимания)

Модель Cynefin (в переводе с валлийского – “среда обитания”, произносится как “куневин”, но чаще можно услышать “кеневин”) предназначена для определения среды, в которой требуется осуществлять управление и выбора адекватных ей стратегий управления. Модель определяет пять сред.

Среды Cynefin

Пять сред Cynefin
  1. Простая (Clear, Simple, Obvious)
    • Цели в этой среде чёткие, конкретные, измеримые. Легко могут быть поставлены по SMART.
    • Причинно-следственные связи просты и очевидны.
    • Бизнес-процессы линейные, деятельность регламентирована.
    • Степень неопределённости минимальна.
    • Работа выполняется легко заменяемым и (зачастую) низкоквалифицированным персоналом. В таких средах управление требуется потому, что человека пока что не полностью заменили роботы.
    • Релевантными этой среде являются разновидности директивного управления и соответствующие инструменты – регламенты, объективные метрики, жёсткий контроль.
  2. Сложная (Complicated)
    • Цели в этой среде обычно менее чёткие, чем в простых, но как правило всё-таки могут быть поставлены по SMART.
    • Причинно-следственные связи сложны и неочевидны.
    • Бизнес-процессы вариативные.
    • Степень неопределённости небольшая.
    • Работа в такой среде выполняется экспертами (инженерами) – людьми, обладающими специфическими знаниями, пониманием причинно-следственных связей и опытом. От выбора экспертов сильно зависит успешность управления.
    • Планирование работы может быть проведено на весь проект с довольно высокой статистической достоверностью. Отслеживание выполнения плана работ может косвенно свидетельствовать о состоянии проекта.
    • Релевантными этой среде являются разновидности классического проектного управления и соответствующие инструменты: CPM, CCPM, PMI, PRINCE, Waterfall.
  3. Запутанная (Complex)
    • Цели в этой среде обычно не чёткие и не могут быть поставлены по SMART, но вместо них есть метрики, по которым можно судить о “успешности”.
    • Причинно-следственные связи либо сложны настолько, что превышают возможности экспертов, либо неизвестны.
    • Степень неопределённости высокая.
    • Работа в такой среде выполняется в основном инженерами и учёными прикладного профиля. Характер деятельности в таких средах – проектный.
    • Работы не могут быть распланированы на весь проект хоть с какой-то достоверностью. Эффективным является планирование методом набегающей волны (Rolling Wave Planning).
    • Релевантными этой среде являются разновидности гибкого проектного управления, позволяющие проводить исследования и ставить эксперименты. Это семейство Agile и соответствующие инструменты: Scrum, DSDM, XP, Kanban, Burndown charts. Критически важным для успеха является постоянное отслеживание метрик успешности проекта и в случае отклонений – быстрая корректировка плана.
  4. Хаотическая (Chaotic).
    • Цели в этой среде поставлены быть не могут, метрик успешности нет. Обычно (кроме, разве что, фундаментальной науки) руководители стараются снизить ущерб и препятствовать распространению этой среды на то, что ей ещё не затронуто.
    • Причинно-следственные связи неизвестны.
    • Степень неопределённости – крайне высокая.
    • Работы выполняются учёными.
    • Планирование принципиально невозможно.
    • Поскольку это так называемая “зона инноваций”, существующие методики управления неприменимы, инструментов управления нет. Считается, что в таких средах их надо либо изобретать новые, либо просто действовать случайным образом.
  5. Беспорядочная (Disordered, Confused). Среда, не соответствующая ни одной из вышеописанных. Управление в ней невозможно.

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

Условное соответствие между областями человеческой деятельности и их оптимальными средами такое:

  1. Хаотическая среда – фундаментальная наука, научно-исследовательская деятельность, инновации.
  2. Запутанная среда – прикладная наука, НИР, стартапы.
  3. Сложная среда – проектная деятельность, строительство, ОКР.
  4. Простая среда – операционная деятельность, производство, массовое обслуживание

Как несложно заметить, этот процесс описывает эволюцию человеческого знания. В апреле 1938 года химик Рой Планкетт случайно обнаружил, что закачанный им в баллоны под давлением газообразный тетрафторэтилен спонтанно полимеризовался в белый парафиноподобный порошок, который ныне известен как тефлон (хаотическая среда). Полученный полимер оказался интересным и в DuPont были проведены работы по поиску технологии дешёвого синтеза тефлона в промышленных масштабах (запутанная среда), а к 1951 году были построены заводы (сложная среда) и производство посуды с тефлоновым покрытием стало крупносерийным (простая среда).

Модель Кеневин предполагает, что среда постоянно меняется под воздействием внешних и внутренних факторов, и для эффективного управления надо: во-первых, постоянно анализировать среду; во-вторых, воздействовать на неё для приведения к желаемому виду. Путь этих изменений образует цикл (по-моему, “водоворот” точнее).

Цикл Cynefin

Цикл Cynefin
  1. Хаотическая среда станет запутанной, если удастся найти или выбрать желаемые метрики или критерии успешности (свойства тефлона изучены, теперь надо научиться производить его дёшево и много).
  2. Запутанная среда станет сложной, если удастся изучить её и выявить закономерности (подготовка техпроцесса промышленного производства тефлона). Люди, знающие эти закономерности становятся экспертами.
  3. Сложная среда станет простой, если её удастся формализовать, стандартизовать и регламентировать (организация заводов по производству тефлона и изделий с его использованием).
  4. Простая среда станет хаотической при потере контроля.
  5. Все среды стремятся приобрести случайные свойства друг друга и стать беспорядочными.
  6. На беспорядочную среду можно воздействовать с целью, её трансформации в одну из четырёх управляемых.

Алгоритм управления с использованием Cynefin выглядит простым:

  1. Понять текущую среду и выбрать желаемую
  2. Трансформировать текущую среду в желаемую
  3. Выбрать и реализовать стратегию управления
  4. Повторить

Лично я ни разу не видел управление по Cynefin. А хотелось бы.

Динамическая сборка ICU4С 69.1

Логическое дополнение этой статьи.

Нам понадобятся:

  1. MinGW
  2. MSYS2
  3. Python

Приступаем к сборке.

  1. Переходим по ссылке, выбираем нужную версию и скачиваем архив с исходным кодом (icu4c-69_1-src.zip)
  2. Распаковываем, например в корень T:\
  3. В icu\source\common\unicode\uconfig.h сразу после header guards добавляем строчку
    #define U_DISABLE_RENAMING 1
    чтобы получилось примерно так:
// © 2016 and later: Unicode, Inc. and others.
// License & terms of use: http://www.unicode.org/copyright.html
/*  
**********************************************************************
*   Copyright (C) 2002-2016, International Business Machines
*   Corporation and others.  All Rights Reserved.
**********************************************************************
*   file name:  uconfig.h
*   encoding:   UTF-8
*   tab size:   8 (not used)
*   indentation:4
*
*   created on: 2002sep19
*   created by: Markus W. Scherer
*/

#ifndef __UCONFIG_H__
#define __UCONFIG_H__

#define U_DISABLE_RENAMING 1

<...>
  1. Запускаем MSYS2 и переходим в /t/icu/source
  2. Настраиваем окружение так, чтобы использовался наш MinGW и Python:
export PATH=/c/dev/8.1.0/x64/mingw/bin:/c/dev/tools/python39:$PATH
  1. Настраиваем флаги компилятора для правильной статической сборки:
export CFLAGS="-DU_DISABLE_RENAMING=1"
export CXXFLAGS="-std=c++11"
  1. Конфигурируем ICU:
 ./runConfigureICU MinGW prefix=/c/dev/8.1.0/x64/icu4c-69_1/shared -enable-shared -disable-static --disable-renaming --host=x86_64-w64-mingw32 --build=x86_64-w64-mingw32 --target=x86_64-w64-mingw32 --with-data-packaging=library
  1. Собираем:
mingw32-make

Сборка должна завершиться успешно.

  1. Устанавливаем библиотеку:
mingw32-make all install
  1. Копируем библиотеки в директорию lib:
cp /c/dev/8.1.0/x64/icu4c-69_1/shared/bin/*.dll /c/dev/8.1.0/x64/icu4c-69_1/shared/lib/

Всё.

Если используется система сборки qmake, в pro файле библиотека подключается так:

LIBS += -LC:/dev/8.1.0/x64/icu4c-69_1/shared/lib/
LIBS += -licuin
LIBS += -licutest
LIBS += -licutu
LIBS += -licuio
LIBS += -licuuc
LIBS += -licudt

INCLUDEPATH += C:/dev/8.1.0/x64/icu4c-69_1/shared/include
DEPENDPATH += C:/dev/8.1.0/x64/icu4c-69_1/shared/include

Статическая сборка ICU4С 69.1

Что хочу? Получить статическую сборку icu, без внешних зависимостей от библиотек и файлов данных. В качестве подопытной буду использовать программу, которая должна правильно посчитать длину строки в “китайских буквах” 😉

#include <iostream>
#include <fstream>
 
#include <unicode/unistr.h>
#include <unicode/locid.h>
#include <unicode/brkiter.h>
 
int main() {
    const icu::UnicodeString str(L"नमस्ते");
 
    UErrorCode err = U_ZERO_ERROR;
    std::unique_ptr<icu::BreakIterator> iter(icu::BreakIterator::createCharacterInstance(icu::Locale::getDefault(), err));
    if (U_FAILURE(err)) {
        std::cout << u_errorName(err) << std::endl;
        return -1;
    }
    iter->setText(str);
 
    int count = 0;
    while (iter->next() != icu::BreakIterator::DONE) {
        ++count;
    }
    std::cout << std::string((3==count)?"GREAT SUCCESS":"EPIC FAIL") << std::endl;
    return 0;
}

Прежде всего нам понадобятся:

  1. MinGW
  2. MSYS2
  3. Python

Приступаем к сборке.

  1. Переходим по ссылке, выбираем нужную версию и скачиваем архив с исходным кодом (icu4c-69_1-src.zip)
  2. Распаковываем, например в корень T:\
  3. В icu\source\common\unicode\uconfig.h сразу после header guards добавляем строчку
    #define U_DISABLE_RENAMING 1
    чтобы получилось примерно так:
// © 2016 and later: Unicode, Inc. and others.
// License & terms of use: http://www.unicode.org/copyright.html
/*  
**********************************************************************
*   Copyright (C) 2002-2016, International Business Machines
*   Corporation and others.  All Rights Reserved.
**********************************************************************
*   file name:  uconfig.h
*   encoding:   UTF-8
*   tab size:   8 (not used)
*   indentation:4
*
*   created on: 2002sep19
*   created by: Markus W. Scherer
*/

#ifndef __UCONFIG_H__
#define __UCONFIG_H__

#define U_DISABLE_RENAMING 1

<...>
  1. Запускаем MSYS2 и переходим в /t/icu/source
  2. Настраиваем окружение так, чтобы использовался наш MinGW и Python:
export PATH=/c/dev/8.1.0/x64/mingw/bin:/c/dev/tools/python39:$PATH
  1. Настраиваем флаги компилятора для правильной статической сборки:
export CFLAGS="-DU_STATIC_IMPLEMENTATION=1 -DU_DISABLE_RENAMING=1 -static"
export CXXFLAGS="-DU_STATIC_IMPLEMENTATION=1 -static-libstdc++ -static -std=c++11"
  1. Конфигурируем ICU:
./runConfigureICU MinGW prefix=/c/dev/8.1.0/x64/icu4c-69_1/static -disable-shared -enable-static --disable-renaming --host=x86_64-w64-mingw32 --build=x86_64-w64-mingw32 --target=x86_64-w64-mingw32 --with-data-packaging=static
  1. Пытаемся собрать:
mingw32-make

У меня сборка сломается на makeconv из-за того, что имена статических библиотек icu отличаются от ожидаемых

x86_64-w64-mingw32-g++ -O3 -DU_STATIC_IMPLEMENTATION=1 -static-libstdc++ -static -std=c++11 -W -Wall -pedantic -Wpointer-arith -Wwrite-strings -Wno-long-long -mthreads    -o ../../bin/makeconv.exe gencnvex.o genmbcs.o makeconv.o ucnvstat.o -L../../lib "-licutu" -L../../lib "-licuin" -L../../lib "-licuuc" -L../../stubdata "-licudt" -lpthread -lm
C:/dev/8.1.0/x64/mingw/bin/../lib/gcc/x86_64-w64-mingw32/8.1.0/../../../../x86_64-w64-mingw32/bin/ld.exe: cannot find -licutu
C:/dev/8.1.0/x64/mingw/bin/../lib/gcc/x86_64-w64-mingw32/8.1.0/../../../../x86_64-w64-mingw32/bin/ld.exe: cannot find -licuin
C:/dev/8.1.0/x64/mingw/bin/../lib/gcc/x86_64-w64-mingw32/8.1.0/../../../../x86_64-w64-mingw32/bin/ld.exe: cannot find -licuuc
C:/dev/8.1.0/x64/mingw/bin/../lib/gcc/x86_64-w64-mingw32/8.1.0/../../../../x86_64-w64-mingw32/bin/ld.exe: cannot find -licudt
collect2.exe: error: ld returned 1 exit status
mingw32-make[2]: *** [Makefile:81: ../../bin/makeconv.exe] Error 1
mingw32-make[2]: Leaving directory 'T:/icu/source/tools/makeconv'
mingw32-make[1]: *** [Makefile:47: all-recursive] Error 2
mingw32-make[1]: Leaving directory 'T:/icu/source/tools'
mingw32-make: *** [Makefile:153: all-recursive] Error 2

поэтому…

  1. Переименовываем библиотеки:
mv lib/lib{s,}icuin.a
mv lib/lib{s,}icuio.a
mv lib/lib{s,}icutu.a
mv lib/lib{s,}icuuc.a
mv stubdata/lib{s,}icudt.a
  1. Продолжаем сборку:
mingw32-make

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

  1. Копируем библиотеку данных на место заглушки:
cp lib/libsicudt.a stubdata/libsicudt.a
cp lib/libsicudt.a stubdata/libicudt.a
  1. Удаляем собранные исполнимые файлы, поскольку они собраны с заглушкой:
rm ./bin/*
  1. Повторно собираем исполнимые файлы и, наконец, устанавливаем библиотеку:
mingw32-make all install

Всё. Можно скомпилировать и запустить нашу программу.

$ g++ main.cpp -o main -static -L/c/dev/8.1.0/x64/icu4c-69_1/static/lib/ -I/c/dev/8.1.0/x64/icu4c-69_1/static/include -lsicuuc -lsicudt
$ ./main.exe
GREAT SUCCESS
$

Если используется система сборки qmake, в pro файле библиотека подключается так:

LIBS += -LC:/dev/8.1.0/x64/icu4c-69_1/static/lib/
LIBS += -lsicuin
LIBS += -lsicutest
LIBS += -lsicutu
LIBS += -lsicuio
LIBS += -lsicuuc
LIBS += -lsicudt

INCLUDEPATH += C:/dev/8.1.0/x64/icu4c-69_1/static/include
DEPENDPATH += C:/dev/8.1.0/x64/icu4c-69_1/static/include

MinGW

MinGW я не собираю, но и не устанавливаю традиционным способом 🙂

  1. Проходим по ссылке и выбираем инструменты для сборки (Toolchains targetting) нужной разрядности (например x64)
  2. Выбираем версию (я выбрал MinGW-W64 GCC-8.1.0)
  3. Выбираем вариант модели потоков (win/posix) и обработки исключений (seh/sjlj). Я выбрал x86_64-posix-sjlj
  4. Скачиваем архив (в моём случае – x86_64-8.1.0-release-posix-sjlj-rt_v6-rev0.7z)
  5. Распаковываем его.

Я распаковал его в c:\dev\8.1.0\x64\mingw соответственно.

Проверяю

Моё окружение разработчика и как его собрать

В основном в разработке у меня используются следующие инструменты:

  1. MinGW
  2. Qt
  3. git
  4. Разные библиотеки

Все инструменты у меня располагаются в C:\dev\, вынутри которой есть одна поддиректория ‘tools’ (с полезными инструментами) и несколько директорий с названиями версий MinGW (на данный момент 7.3.0 и 8.1.0), внутри которых находятся директории с инструментами разной разрядности (x32 и x64).

Для наглядности приведу пример структуры:

├───7.3.0
│   ├───x32
│   │   ├───mingw
│   │   └───Qt
│   │       ├───5.12.3
│   │       │   ├───shared
│   │       │   └───static
│   │       ├───5.13.1
│   │       │   ├───shared
│   │       │   └───static
│   │       └───5.5.1
│   │           ├───shared
│   │           └───static
│   └───x64
│       ├───boost
│       │   ├───1.71.0
│       │   │   └───static
│       │   └───1.72.0
│       │       ├───shared
│       │       └───static
│       ├───icu4c-57.2
│       │   ├───shared
│       │   └───static
│       ├───libcurl
│       ├───mingw
│       ├───postgresql-11.1
│       ├───sqlite_3200100
│       └───zlib-1.2.11
├───8.1.0
│   └───x64
│       ├───mingw
│       ├───openssl-1.1.1k
│       └───Qt
│           └───5.15.2
│               ├───shared
│               └───static
└───tools
    ├───cmake-3.14.0-rc2-win64-x64
    ├───dbeaver
    ├───DrMemory-Windows-2.1.0-1
    ├───git-2.20.1-64
    ├───Python39
    ├───QtCreator-4.13.0
    ├───QtCreator-4.15.0
    └───SQLiteStudio 3.2.1

Дальше планирую записывать как получить тот или иной инструмент в окружении:

  1. MinGW

Пример мышления в терминах процесса

В книге Арно Лоре “Проектирование веб-API” (5.3.1 Построение простой цепочки целей) есть пример API для мобильного банкинга, где клиент пытается перевести деньги бенефециару, справляясь с трудностями бизнес-логики (сумма превышает безопасный лимит; сумма превышает совокупный дневной лимит перевода; исходный счет нельзя использовать в качестве источника перевода; данный вариант назначения нельзя использовать с этим источником). На картинках рассаматривается такая эволюция целей API:

Рис. 5.14. Денежный перевод

Но разве нельзя сделать это короче и более плавно, предотвращая ошибки?

Рис. 5.15. Предотвращение ошибок в потоке денежных переводов

С помощью этой новой цели можно предотвратить три из четырех ошибок!

Рис. 5.16. Для составления списка вариантов назначения из выбранного источника требуется один вызов

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

В этом сценарии клиент последовательно вполняет три шага:

  1. Получает список источников
  2. Получает список назначений для выбранного источника
  3. Переводит выбранную сумму из выбранного источника в выбранное назначение

Псевдокод:

sources = API.getSources()
destinations = API.getDestinations(source[i])
API.makeTransfer(sources[i], destinations[j], amount)

Не смотря на то, что на рисунках приуствтует слово “цель” эволюция API движется вопросом “как?”, а не “зачем?”.

Получается API, который выглядит так, будто цель клиента при переводе денег – это избавиться от денег. Обычно переводы имеют внешнюю цель – финансовое обеспечение каких-то выгод (оплата заказов, внесение депозита). То есть они используются не для того, чтобы денег где-то не стало, а чтобы они где-то появились. Из этого следует, что изначально клиент знает назначение и сумму перевода, а источник определяется исходя из возможностей. Почему бы не использовать ценарий, в котором клиент:

  1. Получает список назначений
  2. Получает список источников для выбранного назначения
  3. Переводит выбранную сумму из выбранного источника в выбранное назначение

Псевдокод:

destinations = API.getDestinations()
sources = API.getSources(destinations[j])
API.makeTransfer(sources[i], destinations[j], amount)

Отчасти это решается “последней оптимизацией”, которая представлена ниже.

Рис. 5.17 Один вызов предоставляет все данные, необходимые для выбора источника и варианта назначения

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

Этот вариант предлагает получить все возможные пары (источник, назначение) и после этого перевести деньги. И он приемлем, пока источников и назначений мало. Например, при наличии трёх источников и десяти назначений количество пар может достигнуть 30. Передавать клиенту много данных без необходимости не особо хорошая идея, кроме того, это вынудит клиента решать проблемы их отображения и фильтрации (что критиковалось в 5.3.2).

Re: Нужны ли в API цифровые коды?

Интересный вопрос поднялся тут https://mxjournal.ru/blog/1481.

По сути своей API – задаёт протокол обмена данными между разными программами (программа, предоставляющая API – поставщик, а программа, его использующая – клиент). Поскольку API (не ограничиваясь вебом) всё-таки программный интерфейс при его проектировании нужно ориентироваться на удобство использования со стороны программного клиента.

Протокол обмена в свою очередь – это форматы данных и сценарии обмена данными. Концентрируя внимание на форматах данных, можно заметить, что их обычно разделяют на текстовые и двоичные. И в целом текстовые более удобны для человека, а двоичные для программ.

Текстовые форматы:

  • удобны для чтения человеком
  • более требовательны по памяти
  • генерируют больше трафика при передаче
  • сложнее обрабатываются программно

Двоичные форматы:

  • нечитаемы человеком
  • занимают меньше места
  • проще обрабатываются программно

Есть и компромиссные варианты текстовых форматов, ориентированных на машинную обработку (различной степени читаемости человеком) – XML, YAML или используемый в исходном посте JSON.

Что касается интернета в целом и веба в частности, то широкое распространение текстовых протоколов в 21 веке обусловлено в основном историческими причинами.

Когда компьютеры были большими, а оперативная память маленькая, компьютеры были настолько разными, что ни о какой совместимости между ними и речи не шло. И разница была не только в системах команд или порядке байт в машинных словах – сам размер байта был разным (6, 7, 8, 9, 32, 36 бит). Ещё одной особенностью компьютеров тех времён было то, компьютер от пользователя отделяла линия связи и текстовый терминал, где пользователь мог вводить текстовые данные и видеть вывод также в текстовом виде.

Монополия IBM на рынке компьютеров позволила широко распространить 8-битный байт, а затем и адресацию байтами (а не машинными словами). Основной причиной для введения байтовой адресации была как раз сложность обработки текстовых данных, вводимых пользователями. Эту боль можно испытать и сейчас, например попытавшись обрабатывать текст на TigerSHARC (он может адресовать только машинные слова по 32 бита).

Затем появился стандарт ASCII, определивший 128 символов алфавита и ставящий им в соответствие числовое значение от 0 до 127. Восьмой бит поначалу использовался для контроля ошибок при передаче информации, а когда необходимость в этом отпала – для расширения кодов ASCII символами национальных алфавитов и другими “нужными” знаками (и начался encoding hell).

Затем вместо терминалов стали подключать другие компьютеры и так появились первые текстовые протоколы обмена.

Потом появилась ARPANET, электронная почта и UUCP,RFC822, SMTP, MIME и всё заверте…

Так что современный текстовый веб – это наследие отцов-основателей из давних времён.

Программы обрабатывают данные во внутреннем представлении, которое использует доступные в языке типы: целые и дробные числа, структуры, массивы, объекты классов и т. д. массивы, объекты. Типичная схема работы программы, входные и выходные данные для которой имеют текстовый формат:

  1. десериализация: входные данные преобразуются во внутреннее представление
  2. обработка: входные данные преобразуются в выходные данные (также во внутреннем преставлении)
  3. сериализация: выходные данные преобразуются в текстовый формат

Два из этих трёх шагов (1 и 3) не имеют непосредственного отношения к выполнению полезной работы. Но случается так, что для реализации этих шагов требуется писать достаточно много кода. В качестве примера недавно на работе я писал программу для конвертирования из двоичного формата в CSV. Структура программы получилась такая:

  1. ~14,5 килобайт (~650 строк) на описание структур и чтение двоичного формата
  2. ~1,2 килобайт (~70 строк) на преобразование и декодирование двоичных значений в строки
  3. ~16,5 килобайт (~900 строк) на запись в CSV

Если бы исходный формат был текстовым, десериализация потребовала бы ещё больше кода. А больше кода – больше ошибок 🙂

Исходный двоичный файл тем временем при конвертации в CSV увеличился в размере почти в четыре раза.

Поэтому для программных интерфейсов имеет смысл в первую очередь рассматривать двоичные форматы, чтобы уменьшить количество ошибок и лишнего кода. Эта идея была доведена до реализации, например в gRPC (HTTP/2, protobuf), однако в нём код сериализации/десериализации генерируется, а не пишется руками.

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

В исходном посте предлагаются три формата статуса на основе JSON.

Первый вариант: "status": "в работе"

Здесь мне не нравится что:

  1. строки могут отдаваться на разных языках (например, в зависимости от заголовка Accept-Language)
  2. разработчик клиентской программы может не владеть языком, на котором написано сообщение, и оно будет для него менее понятным, чем число (в мире больше людей, которые знают арабские числа, чем любой естественный язык)
  3. строить логику обработки ориентируясь на значение строк в общем случае проблемно:
  • не во всех языках есть switch/case для строк (Си – яркий пример неудобства работы со строками) или ассоциативные массивы, в которых ключами будут строки, а значениями – код (lambda или указатели на функции), который обрабатывает этот статус.
  • строки могут различаться кодировками, и даже использование символов только ASCII тут не спасает (любая однобайтная кодировка гарантировано не совпадёт с UTF-16 и UTF-32).
  • сравнение строк выполняется дольше, чем сравнение чисел, которые они представляют
  • Строки могут превратиться в нечитаемые без особых на то причин. Например, в JSON значение строки заключено в кавычки. А если значение строки должно содержать символ кавычки? Появляется волшебный символ, за ним стаффинг или экранирование и…
{
    "KEY\\t\/\"": "VALUE\\t\/\r\n\"",
    "0": "\u0000",
    "1": "\u0001",
    "31": "\u001f"
}

приятного чтения!

Второй вариант: "status_code": 4, "status_name": "в работе"

Этот вариант лучше первого, потому что в клиентском коде поле status_name можно просто проигнорировать, и выстроить логику работы на основе значения status_code. Однако выделять два поля одной семантической единицы (статуса) я бы не стал. Судя по синтаксису, статус является одним из полей какой-то более крупной сущности (допустим ответа) и два поля, относящиеся к статусу без причины загрязняют структуру ответа. JSON позволяет объединять эти два поля в одну структуру – и грех этим не воспользоваться!

Третий вариант: "status": { "code": 4, "name": "в работе" }

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