Непозволительно высокая цена плохого API
В PostgreSQL есть конфиг с параметрами запуска СУБД — postgresql.conf. В нём параметры заданы в формате key = value. Например,
port = 5432
max_connections = 100
authentication_timeout = 1min
А ещё в PostgreSQL можно создавать расширения — по сути плагины. Если расширение хочет доставать из postgresql.conf какие-то свои параметры, оно должно определить их с помощью функции DefineCustomStringVariable. Вот пример из https://github.com/postgres/postgres/blob/e0fa5bd146564d9d05cac15bdcba65b7860d2b91/contrib/basic_archive/basic_archive.c#L67:
void _PG_init(void) {
DefineCustomStringVariable(
"basic_archive.archive_directory",
"Archive file destination directory.",
NULL,
&archive_directory,
"",
PGC_SIGHUP,
0,
check_archive_directory, NULL, NULL);
MarkGUCPrefixReserved("basic_archive");
}
Здесь задаётся название параметра, указатель на переменную, в которую надо записать его значение, хук проверки корректности значения и т.д. Главное, на что я хочу обратить ваше внимание, — этот вызов выглядит максимально декларативно. Начиная с названия — DefineCustomStringVariable — и заканчивая тем, что мы передаём туда пачку параметров, которые влияют на разные стадии времени жизни сервера СУБД. Видя такой вызов, я ожидаю, что он добавит это всё в какой-то список параметров конфига. А применяться это будет когда-то потом, когда мы будем парсить конфиг. Более того, если https://github.com/postgres/postgres/blob/e0fa5bd146564d9d05cac15bdcba65b7860d2b91/src/backend/utils/misc/guc.c#L5129, она, на первый взгляд, так и выглядит.
Зачем ты всё это рассказываешь?
postgresql.conf читается при старте сервера СУБД. Если в нём есть ошибки (например, указан путь к несуществующему файлу), надо аварийно прервать запуск сервера и сообщить об ошибке в лог. Кроме того postgresql.conf можно явно перечитать, когда сервер уже запущен. В этом случае об ошибках надо просто сообщить в лог, сохранив прежние значения параметров и продолжив работу.
У меня была задача реализовать такое поведение для параметра в моём расширении. Я был уверен, что DefineCustomStringVariable делает такое из коробки, потому что расширения для PostgreSQL создаются уже десятки лет...
Как же я был не прав... Я не буду перегружать вас, рассказывая весь свой путь, перейду сразу к финалу:
👉 у меня ушло примерно 2 часа — 2 часа, чтобы добавить поддержку нового параметра в конфиге, Карл!
👉 оказалось, что DefineCustomStringVariable сразу выполняет парсинг параметра из конфига, запуск всех хуков и инициализацию переменной, а не просто добавляет параметр в список. И важно, чтобы парсинг работал в режиме «сообщи об ошибке и продолжи со старым значением»
👉 чтобы парсинг падал при старте СУБД, нужно отдельно вручную вызывать его в режиме «fail fast»
Итого я считаю, что DefineCustomStringVariable — пример плохого API, который, во-первых, не позволяет решить простую типовую задачу быстро и легко, а во-вторых, требует детального понимания своей реализации, чтобы пользоваться им правильно.
Ок, а правильно-то как?
Мне в голову приходят две идеи.
1. Переименовать в DefineAndTryParseCustomStringVariable. Да, название перегружено, но оно хотя бы явно даёт понять, что парсить будем сразу. Это тут же поднимает вопрос, что происходит, если распарсить не удалось, и позволяет быстрее прийти к пониманию, что для «fail fast» надо писать отдельный код
2. Добавить отдельную функцию для use case «добавить параметр расширения» — AddExtenstionGuc(...). Внутри она бы делала DefineCustomStringVariable, а потом вызывала бы парсинг в режиме «fail fast». Неидеально, но решает пользовательскую задачу «добавь параметр конфига правильно».
❗️Итого, неудачный API design работы с параметрами конфига в расширениях стоил мне 2 часов рабочего времени, которые я мог бы уделить менее тривиальным задачам. Пока все программисты не превратились в тимлидов, у которых кода касаются только AI-агенты, за удобством и интуитивностью API всё ещё актуально следить.