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

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

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

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

  • Добавление новых библиотек быстро увеличивает размер проекта. В нашу эпоху быстрого интернета и больших SSD дисков это, конечно, не большая проблема. Но, довольно неприятно, когда время загрузки превращается в 10 минут вместо 1 из-за системы контроля версий.
  • Даже если вы используете всего 1% возможностей библиотеки, обычно она включена в проект целиком. В результате, если библиотеки используются в виде готовых модулей (например, DLL), размер дистрибутива растет очень быстро. Если использовать библиотеку как исходный код, то время компиляции значительно увеличивается.
  • Инфраструктура, связанная с компиляцией проекта, усложняется. Для некоторых библиотек требуются дополнительные компоненты. Простой пример: нам нужен Python для сборки. В результате через какое-то время вам понадобится куча дополнительных программ для сборки проекта. Так возрастает вероятность того, что что-то не получится. Это сложно объяснить, это нужно испытать. В больших проектах постоянно что-то не получается и приходится прикладывать усилия, чтобы все заработало и скомпилировалось.
  • Если вам небезразличны уязвимости, вы должны регулярно обновлять сторонние библиотеки. Нарушителям интересно изучать библиотеки кода для поиска уязвимостей. Во-первых, многие библиотеки открыты, а во-вторых, найдя слабое место в одной из библиотек, можно получить отмычку ко многим приложениям, где используется библиотека.
  • У вас будут проблемы с обновлением до новой версии компилятора. Определенно будет несколько библиотек, которые не будут готовы адаптироваться к новому компилятору. И вам придется подождать или внести свои собственные исправления в библиотеке.
  • У вас будут проблемы при переходе на другой компилятор. Например, вы используете Visual C++ и хотите использовать Intel C++. Наверняка найдется пара библиотек, где что-то не так.
  • У вас будут проблемы с переходом на другую платформу. Не обязательно даже совершенно другая платформа. Допустим, вы решили портировать Win32-приложение на Win64. У вас будут те же проблемы. Скорее всего, несколько библиотек будут к этому не готовы и вам будет интересно, что с ними делать. Особенно неприятно, когда библиотека где-то дремлет и больше не развивается.
  • Рано или поздно, если вы используете множество библиотек C, в которых типы не хранятся в пространстве имен, у вас начнутся конфликты имен. Это вызывает ошибки компиляции или скрытые ошибки. Например, неправильная константа перечисления может быть использована вместо той, которую вы запланировали.
  • Если в вашем проекте используется много библиотек, добавление еще одной не покажется вредным. Можно провести аналогию с теорией разбитых окон. Следовательно, рост проекта превращается в неуправляемый хаос.
  • И может быть гораздо больше других недостатков добавления новых библиотек, о которых я, вероятно, не знаю. Но в любом случае дополнительные библиотеки усложняют поддержку проекта. Некоторые проблемы могут возникать в том фрагменте, где они меньше всего ожидаются.

Опять же, я должен подчеркнуть. Я не говорю, что мы должны вообще отказаться от использования сторонних библиотек. Если нам придется работать с изображениями в формате PNG в программе, мы возьмем библиотеку LibPNG и не будем изобретать велосипед.

Но даже работая с PNG, нам нужно остановиться и подумать. Нужна ли нам библиотека? Что мы хотим сделать с изображениями? Если стоит задача просто сохранить изображение в файл *.png, можно обойтись системными функциями. Например, если у вас есть приложение для Windows, вы можете использовать WIC. А если вы уже используете библиотеку MFC, то нет необходимости усложнять код, потому что есть класс CImage (см. обсуждение на StackOverflow). Минус одна библиотека-отлично!

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

Можно было «втиснуть» уже существующие библиотеки.
Было понятно, что все они будут лишними, но регулярные выражения все же были нужны и надо было что-то придумывать.

Совершенно случайно именно в этот момент я читал книгу «Красивый код» (ISBN 9780596510046). Эта книга о простых и элегантных решениях. И там я наткнулся на предельно простую реализацию регулярных выражений. Всего несколько десятков строк. И это все!

Я решил использовать эту реализацию в PVS-Studio. И знаешь, что? Возможностей этой реализации нам пока хватает. А какие-то сложные регулярные выражения нам просто не нужны.

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

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

Вероятно, читателям будет интересно узнать, что представлял собой код для поиска регулярных выражений. Мы напечатаем это здесь из книги.
Посмотрите, как это изящно. Этот код был немного изменен при интеграции с PVS-Studio, но суть его осталась неизменной. Итак, код из книги:

// regular expression format
// c Matches any "c" letter
// .(dot) Matches any (singular) symbol 
// ^ Matches the beginning of the input string
// $ Matches the end of the input string
// * Match the appearance of the preceding character zero or
// several times
int matchhere(char *regexp, char *text);
int matchstar(int c, char *regexp, char *text);
// match: search for regular expression anywhere in text
int match(char *regexp, char *text)
{
  if (regexp[0] == '^')
    return matchhere(regexp+1, text);
  do { /* must look even if string is empty */
   if (matchhere(regexp, text))
     return 1;
  } while (*text++ != '\0');
  return 0;
}
// matchhere: search for regexp at beginning of text 
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: search for c*regexp at beginning of text
int matchstar(int c, char *regexp, char *text)
{
  do {   /* * a * matches zero or more instances */
            more instances */
    if (matchhere(regexp, text))
      return 1;
  } while (*text != '\0' && (*text++ == c || c == '.'));
  return 0;
}

Рекомендация

Не спешите добавлять в проект новые библиотеки. Добавляйте только тогда, когда нет другого способа обойтись без библиотеки.

Вот некоторые возможные обходные пути:

  • Посмотрите, есть ли в API вашей системы или в одной из существующих библиотек необходимая функциональность. Это хорошая идея, чтобы изучить этот вопрос.
  • Если вы планируете использовать небольшой кусок функционала из библиотеки, то имеет смысл реализовать его самостоятельно. Аргумент добавить библиотеку «на всякий случай» не годится. Почти наверняка эта библиотека не будет часто использоваться в будущем. Программисты иногда хотят иметь универсальность, которая на самом деле не нужна.
  • Если для решения вашей задачи есть несколько библиотек, выберите самую простую, соответствующую вашим требованиям. Как я уже говорил ранее, избавьтесь от мыслей «это классная библиотека — возьму на всякий случай».
  • Прежде чем добавлять новую библиотеку, просто расслабьтесь и подумайте. Можно даже сделать перерыв, выпить кофе, обсудить с коллегами. Возможно, вы увидите, что проблему можно решить совсем по-другому, без использования сторонних библиотек.

P.S. Вещи, о которых я говорю здесь, могут быть не очень приемлемы для всех. Например, моя рекомендация использовать не переносимую универсальную библиотеку, а WinAPI. Могут возникнуть возражения, основанные на том, что такой путь «привязывает» этот проект к одной операционной системе. И тогда сделать программу портативной будет очень сложно. Но я с этим не согласен. Довольно часто идея «а потом портируем на другую операционную систему» ​​существует только в голове у программиста. Такая задача может быть даже ненужной для менеджеров. Другой вариант — проект сдохнет из-за своей сложности и универсальности, прежде чем наберет популярность и необходимость портирования. Также не забывайте про пункт (7) в списке проблем, приведенном выше.