вторник, 30 марта 2010 г.

[prog.thoughts] О C++ных библиотеках вообще, и о Boost-е в частности

Попрограммировав недавно на Java, я понял “в чем сила” Java – в IDE и библиотеках. Поскольку IDE без библиотек мало чего стоит, то получается, что самым главным достоинством Java является огромное количество готовых библиотек. Но не только наличие, а еще и простота, с которой сторонние библиотеки подключаются к Java-проектам. Берешь jar/zip файл, прописываешь его в CLASSPATH и все. А с использованием Maven2 даже об этом думать не нужно – достаточно включить упоминание нужной библиотеки в список зависимостей проекта и сам Maven2 скачает ее, установит и пропишет в CLASSPATH (eao197: впрочем, Maven2 видел очень издалека, поэтому могу идеализировать).

Нам, C++никам, такая простота подключения чужой библиотеки даже не снилась. И это не есть хорошо. Следует что-то подправить в консерватории и идущий ниже текст будет описанием моих мыслей о том, “как нам реорганизовать Рабкрин”.

Прежде всего, в C++ не будет такой легкости подключения скомпилированных версий библиотек, как в Java. Все-таки, в Java стандартизированный байт-код, а в C++ множество несовместимых между собой компиляторов на разных платформах. Более того, скомпилированные даже одним компилятором, но с разными опциями, obj-файлы нельзя безопасно использовать в рамках одного проекта.

Поэтому напрашивается первый вывод: C++ные библиотеки должны распространятся в виде исходных текстов.

Конечно, есть множество закрытых библиотек, разработчики которых никогда не захотят открыть исходные тексты. Ну да и фиг с ними, не о том речь. Речь о библиотеках общего назначения – для работы с датой/временем, регулярными выражениями, таймерами, сокетами и другими видами IPC, FTP/HTTP/SMTP/POP3/IMAP и пр. транспортами и т.д., и т.п.

Раз C++ные библиотеки распространяются в виде исходников, значит пользователю придется их компилировать. А раз так, то все библиотеки должны поддерживать какую-то единую систему настройки и сборки на машине пользователя.

Единая система сборки должна быть кроссплатформенной и легкой в изучении/использовании. Исключительно Unix-овые Autotools, а так же заточенные под конкретный компилятор и make-утилиты Makefile идут в топку. Лично мне хотелось бы, чтобы build-tool сам управлял сборкой (как делает SCons), но вполне бы устроил меня и генератор проектных файлов вроде CMake или MPC.

Но всего этого мало. Еще нужна система дистрибуции библиотек и отслеживания зависимостей между ними. Что-то вроде Maven2-репозиториев или RubyGem-ов. Например, я публикую в каком-то репозитории свои проекты cpp_util-2.5.0 и cls-3.1.0, причем cls-3.1.0 зависит от cpp_util-2.5.0. Если кто-то захочет использовать cls-3.1.0, он автоматом должен получить и cpp_util-2.5.0.

Все три составляющих я считаю очень важными. Но самым важным из них является как раз система дистрибуции. Имхо, без нее нельзя будет заниматься разработкой небольших, заточенных под конкретную задачу, библиотек. Например, если мне нужен генератор UUID, то я хочу получить только маленькую библиотеку с минимальным списком зависимостей, а не полутора мегабайтную POCO, десяти мегабайтную ACE или тридцати мегабайтный Boost.

Ну а раз Boost был помянут, то пора вставить ему очередной пистон. Как по мне, сама организация Boost-а напрочь убивает идею маленьких предметно-ориентированных библиотек. То, что процветает в Ruby и Java, в C++ усилиями Boost-оводов становится невозможным. Например, вам нужна библиотека для UUID? Нет проблем – берите Boost. Недорого, всего 30Mb каждая версия. Не хотите столько таскать со своим проектом? Нет проблем – в Boost-е есть bcp, он вам вырежет только то, что нужно. Ах, со временем вам потребовалось еще и filesystem? Ну что же, вновь берете bcp в зубы и старательно выпиливаете из Boost-а UUID вместе с filesystem.

Может быть для старых пользователей Boost-а это и выглядит нормальным. Но для меня это дикость. Если бы Boost изначально не был одной большой помойкой (в самом хорошем смысле этого слова), а состоял из модулей с отслеживанием зависимостей между ними, то ситуация для пользователей вроде меня была бы проще и удобнее. Скажем, есть модуль Boost.Config. От него зависит модуль Boost.Uuid. Если мне нужен только Boost.Uuid, то я скачиваю именно его и получаю в нагрузку только Boost.Config. Когда со временем мне понадобится Boost.Filesystem, я возьму просто Boost.Filesystem и все, т.к. Boost.Config у меня уже есть.

Естественно, у каждого модуля в Boost-е есть свой набор версий. Для каждой версии указывается, с какими версиями зависимостей он был протестирован. Опять же, Boost-оводы имеют возможность выпускать согласованные обновления. Например, все модули версии 1.47.0 взаимно совместимы друг с другом. При этом какая-то библиотека может иметь собственную историю развития до следующего большого релиза. Скажем, пока Boost.Config остается на версии 1.47.0, какой-нибудь Boost.Asio может развиться до 1.47.0.154 за счет баг-фиксов.

Такая схема позволила бы решить еще одну проблему, с которой Boost-оводы начинают сталкиваться: отказ авторов библиотеки от ее дальнейшего развития. Блин, ежу было понятно, что в мире OpenSource никто не обязан тянуть свой проект оставшуюся часть жизни. Написал несколько версий, потом обстоятельства изменились – забросил нафиг. Кому нужно, тот подхватит и продолжит, или же напишет что-то свое, еще лучшее. Это нормальный процесс. Посему модель развития Boost-а нужно было выбирать так, чтобы это учитывать. Если бы Boost проектировался не как одна большая помойка (опять же в лучшем смысле этого слова), а как репозиторий множества версий частично связанных друг с другом библиотек (внимательно смотрим на RubyGems и CPAN), то все бы решилось автоматически. Забросил автор какой-нибудь AnotherBoostLibrary ее развитие на версии 1.48.0 – ну и пофигу. Вот она такая библиотека, версии 1.48.0. Работает она в ваших условиях – отлично, берете и используете. Не работает – не берете, либо допиливаете, либо форкаете и переделываете.

Кстати, возражения о том, что разрозненные версии – это плохо, что здорово, когда все централизовано тестируется и обновляется, и тому подобные попытки защитить Boost-овскую централизацию, идут лесом. В мире Ruby децентрализация и зависимости между библиотеками отлично работают. В мире Java все это так же работает. Мир C++ ничем не хуже. В нем так же сработает. Нужно только понять, что монстры вроде Boost-а, ACE или Qt – это не выход.

Хотя на счет Qt… А не забить ли на все большой болт и не пересесть ли полностью на Qt4? Вдруг там все хорошо? ;)

Отправить комментарий