![Что нам строит дом построить](https://habrastorage.org/getpro/habr/post_images/ba4/f4c/02a/ba4f4c02abf4804e022de248dba9e84a.png)
Собственно, это классический подход в разработке программного обеспечения — повторное использование своих или чужих наработок (сторонних библиотек). И именно этим путём движется большинство программистов.
Однако, теоретики в статьях и книгах, забывают упомянуть, в какой ад превращается поддержка несколько десятков сторонних библиотек, живущих в вашем проекте, скажем по прошествии 10 лет.
Я рекомендую всячески сопротивляться добавлению в проект каждой новой библиотеки. Но прошу понять меня правильно. Я вовсе не говорю, что не надо использовать библиотеки и писать всё самостоятельно. Это просто-напросто глупо. Однако, часто новая библиотека добавляется в проект по прихоти одного разработчика, с целью использовать в ней какую-то маленькую «фитюльку». Добавить новую библиотеку не сложно. Вот только потом всей команде много лет придётся нести груз её поддержки.
Наблюдая за развитием некоторых больших проектов, я могу перечислить ряд проблем наличия большого количества сторонних библиотек. Наверное, я перечислю далеко не все проблемы, но даже следующий список должен побудить вас задуматься:
- Добавление новых библиотек быстро увеличивает размер проекта. В нашу эпоху быстрого интернета и больших SSD дисков это не является существенно проблемой. Однако, когда проект начинает скачиваться из системы контроля версий не за 1 минуту, а за 10, это уже неприятно.
- Даже если вы используете 1% от возможностей библиотеки, как правило в проект она будет включена целиком. В результате, если библиотеки используется в виде готовых модулей (например, DLL), то очень быстро растёт размер дистрибутива. Если вы используете библиотеки в виде исходного кода, то существенно увеличивается время компиляции.
- Усложняется инфраструктура, связанная с компиляцией проекта. Некоторым библиотекам требуются дополнительные компоненты. Простой пример: для сборки требуется наличие Python. В результате через некоторое время для сборки проекта нужно в начале вырастить на компьютере целый сад вспомогательных программ. Возрастает вероятность, что где-то что-то перестанет работать. Объяснить это сложно, это надо прочувствовать. В больших проектах постоянно «отваливается» то одно то другое и нужно постоянно прилагать усилие чтобы всё работало и компилировалось.
- Если вы заботитесь об уязвимостях, вы должны регулярно обновлять сторонние библиотеки. Злоумышленникам выгодно изучать код библиотек с целью поиска уязвимостей. Во-первых, многие библиотеки открыты, а во-вторых, найдя дыру в одной из библиотек можно получить отмычку сразу ко многим приложениям, где эта библиотека используется.
- У вас будут проблемы при переходе на новую версию компилятора. Обязательно будет несколько библиотек, которые не будут торопиться адаптироваться под новый компилятор. И вы будете вынуждены ждать или самим вносить какие-то проявки в библиотеки.
- У вас будут проблемы при переходе на другой компилятор. Например, вы используете Visual C++, а хотите использовать Intel C++. Обязательно найдется пара библиотек, с которыми что-то не заладится.
- У вас будут проблемы при переходе на другую платформу. Не обязательно даже на «сильно другую платформу». Достаточно захотеть превратить Win32 приложение в Win64. У вас будут все те-же проблемы. Несколько библиотек к этому окажутся не готовы и будет непонятно, что с ними делать. Особенно неприятна ситуация, когда библиотека заброшена и более не развивается.
- Рано или поздно, если вы используете множество Си-библиотек, где типы не лежат в namespace, у вас начинают пересекаться имена. Это приводит к ошибкам компиляции или к скрытым ошибкам. Например, начинает использоваться константа не из того enum, из которого вы планировали.
- Если в проекте используется много библиотек, добавление ещё одной не выглядит чем-то вредным. Можно провести аналогию с теорией разбитых окон. В результате разрастание проекта приобретает неконтролируемый характер.
- Есть масса других негативных моментов, о которых я не помню и не знаю. Но в любом случае дополнительные библиотеки очень быстро увеличивают сложность поддержки проекта. Это сложность может проявляться в самых неожиданных местах.
Ещё раз подчеркну. Я не призываю вас отказаться от использования сторонних библиотек. Если в программе вам понадобилось работать с изображениями в формате PNG, то вам надо взять библиотеку LibPNG и не изобретать велосипед.
Но даже работая с PNG, надо остановиться и подумать. А нужна ли библиотека? Какие операции нужно выполнять с изображениями? Быть может, если вся задача сводится к тому, чтобы сохранить какое-то изображение в *.png — файл, можно обойтись системными функциями. Например, если у вас Windows приложение, то вам поможет WIC. А если вы уже используете библиотеку MFC, та вообще не надо усложнять код, ведь есть класс CImagе (см. обсуждение на сайте StackOverflow). Минус одна библиотека — отлично!
Приведу пример из собственной практики. В процессе разработки анализатора PVS-Studio, в паре диагностик потребовалось применять простые регулярные выражения. Вообще, я убеждён, что регулярным выражениям не место в статическом анализе. Это крайне неэффективный подход. Я даже писал статью на эту тему. Однако иногда в какой-то строке нужно бывает что-то найти с помощью регулярного выражения.
Можно было-бы «прикрутить» какую-то из существующих библиотек. Было понятно, что все они будут избыточны, но ведь регулярные выражения все равно нужны и нужно было принять какой-то решение.
Совершенно случайно, именно в тот момент я читал книгу «Beautiful Code» (ISBN 9780596510046). Эта книга о простых и изящных решениях. И в ней я повстречал крайне простую реализацию регулярных выражений. Буквально несколько десятков строк. И всё!
Я взял из книги эту реализацию и начал использовать в PVS-Studio. И знаете, что? До сих пор возможностей этой реализации нам хватает. Какие-то сложные регулярные выражения нам просто не нужны.
Итог. Вместо того, чтобы в проекте появилась какая-то дополнительная библиотека, было потрачено около получаса времени на написания нужной функциональности. Было подавлено желание использовать библиотеку «на все случаи жизни». И оказалось это было правильное решение. Это подтверждается, тем что в течении нескольких лет эта самая функциональность «на все случаи» не понадобилась.
Этот случай окончательно убедил меня, что надо по возможности искать простые решения. По возможности, отказываясь от библиотек вы делаете проект более простым.
Возможно, читателям будет интересно узнать, что же это за такой код для поиска по регулярным выражениям. Перепечатаю его из книги.
Посмотрите, как элегантно. Это код был мной немого изменён при интеграции в PVS-Studio, но его суть не изменилась.
Код из книги
// Формат регулярного выражения.
// c Соответсвует любой букве "с"
// .(точка) Соответсвует любому одному символу
// ^ Соответсвует началу входящей строки
// $ Соответствует концу входящей строки
// * Соответствует появлению предыдущего символа от нуля до
// нескольких раз
int matchhere(char *regexp, char *text);
int matchstar(int c, char *regexp, char *text);
// match: поиск соответствий регулярному выражению по всему тексту
int match(char *regexp, char *text)
{
if (regexp[0] == '^')
return matchhere(regexp+1, text);
do { /* нужно посмотреть даже пустую строку */
if (matchhere(regexp, text))
return 1;
} while (*text++ != '\0');
return 0;
}
// matchhere: поиск соответствий регулярному выражению в начале текста
int matchhere(char *regexp, char *text)
{
if (regexp[0] == '\0')
return 1;
if (regexp[1] == '*')
return matchstar(regexp[0], regexp+2, text);
if (regexp[0] == '$' && regexp[1] == '\0')
return *text == '\0';
if (*text!='\0' && (regexp[0]=='.' || regexp[0]==*text))
return matchhere(regexp+1, text+1);
return 0;
}
// matchstar: поиск регулярного выражения вида с* с начала текста
int matchstar(int c, char *regexp, char *text)
{
do { /* символ * соответствует нулю или
большему количеству появлений */
if (matchhere(regexp, text))
return 1;
} while (*text != '\0' && (*text++ == c || c == '.'));
return 0;
}
Рекомендация. Сопротивляйтесь добавлению в проект новых библиотек. Добавлять следует только когда, когда очевидно, что без библиотеки не обойтись.
Вот некоторые возможные манёвры:
- Быть может нужную функциональность уже предоставляет API вашей системы или одна из уже используемых библиотек. Исследуйте этот вопрос.
- Если вы планируете использовать совсем маленький кусочек функциональности из библиотеки, то есть смысл реализовать его самостоятельно. Аргумент «лучше подключить библиотеку, вдруг потом ещё что-то понадобится» никуда не годится. Почти всегда из этой библиотеки в будущем больше ничего использоваться не будет. Программисты слишком тяготеют к универсальности, которая на самом деле не нужна.
- Если для решения задачи есть несколько библиотек, то выбирайте самую простую, которая удовлетворяет требованиям. Как я писал выше, гоните прочь мысли «на всякий случай взять библиотеку покруче».
- Прежде чем начать добавлять библиотеку, просто подождите и подумайте. Попейте чаю, отвлекитесь, обсудите задачу с коллегами. Возможно в процессе выяснится, что можно решить задачу совсем иным путём, не прибегая к помощи сторонних библиотек.
P.S. Многим рассказанное здесь придется не по душе. Например, то что я рекомендую использовать не переносимую универсальную библиотеку, а допустим WinAPI. На это будут возражения, основанные на том, что тем самым мы привязываем проект к одной операционной системе. И потом будет очень сложно сделать программу переносимой. Я с этим не согласен. Часто идея «потом перенесем на другу операционную систему» живет только в голове разработчика. На самом деле такая задача вообще может быть никогда не поставлена руководством. Или проект «загнётся» из-за излишней сложности и универсальности, ещё до момента популярности и необходимости портирования. Плюс не забывайте пункт (7) в списке проблем, приведенный выше.