Обновленный сборник ужасных советов для C++-разработчиков превратился в целую электронную книгу. Там вы найдете 60 ужасных советов, каждый с пояснением, почему им не стоит следовать. Все будет и в шутку, и всерьез одновременно. Какими бы нелепыми ни казались эти советы, они не выдуманы, а замечены в реальном мире программирования.

Я буду выкладывать сразу по 5 советов, чтобы не утомлять вас — в книге много ссылок на другие интересные статьи, видео и т. д. Однако, если вам не терпится, сразу перейти к полной версии книги: 60 ужасные советы для разработчика C++». В любом случае, приятного чтения!

Ужасный совет N1. Только С++

Настоящие разработчики пишут только на C++!

Нет ничего плохого в написании кода на C++. В мире много проектов, написанных на C++. Ну, например, посмотрите список приложений с домашней страницы Бьярна Страуструпа.

«Вот список систем, приложений и библиотек, которые полностью или большей частью написаны на C++. Естественно, это не полный список. На самом деле, если бы я попытался, я не смог бы перечислить тысячную часть всех основных программ на C++, а этот список содержит, возможно, тысячную часть тех, о которых я слышал. Это список систем, приложений и библиотек, с которыми читатель может быть знаком, которые могут дать новичку представление о том, что делается с C++, или которые я просто считаю "крутыми".

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

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

Язык C++ претендует на звание универсального языка программирования. Однако универсальность не гарантирует, что конкретные приложения будут реализованы быстро и легко. Могут быть языки, которые лучше подходят для проектов, чем другие. Подходящий язык программирования может помочь реализовать проект без значительных затрат времени и сил.

Но нет ничего плохого в том, чтобы разработать небольшую дополнительную утилиту на C++, хотя команде было бы эффективнее использовать для этого другой язык. Затраты на изучение нового языка программирования могут превышать выгоды от его использования.

Иначе обстоит дело, когда команде нужно создать новый, потенциально большой проект. В этом случае команде необходимо обсудить несколько вопросов, прежде чем выбрать язык программирования. Подойдет ли всем известный C++ для поддержки проекта? Не лучше ли выбрать для этого проекта другой язык программирования?

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

Ужасный совет N2. Символ табуляции в строковых литералах

Если вам нужен символ табуляции в строковом литерале, смело нажимайте клавишу табуляции. Сохраните \t … для кого-то другого. Не беспокойтесь.

Я говорю о строковых литералах, где слова должны отделяться друг от друга символом табуляции:

const char str[] = "AAA\tBBB\tCCC";

Кажется, что нет другого способа сделать это. Тем не менее, иногда разработчики просто небрежно нажимают кнопку TAB вместо «\t». Такое бывает в реальных проектах.

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

  • Литералы могут иметь пробелы вместо табуляции. Это зависит от настроек редактора. Хотя код выглядит так, будто есть символ табуляции.
  • Разработчик, который поддерживает код, не сразу поймет, что используется в качестве разделителей — символы табуляции или пробелы.
  • При рефакторинге (или использовании утилит автоформатирования кода) табы могут превращаться в пробелы, что влияет на результат работы программы.

Более того, однажды я увидел код, похожий на этот, в реальном приложении:

const char table[] = "\
bla-bla-bla bla-bla-bla bla-bla-bla bla-bla-bla\n\
      bla-bla-bla       bla-bla-bla\n\
          %s                %d\n\
          %s                %d\n\
          %s                %d\n\
";

Строка разбивается на части с помощью символа «\». Явные символы табуляции смешиваются с пробелами. К сожалению, я не знаю, как это показать здесь, но поверьте мне — код выглядел странно. Выравнивание от начала экрана. Бинго! Я видел много странных вещей при разработке анализатора кода :).

Код должен был быть написан так:

const char table[] =
    "bla-bla-bla bla-bla-bla bla-bla-bla bla-bla-bla\n"
    "     bla-bla-bla       bla-bla-bla\n"
    "         %s\t            %d\n"
    "         %s\t            %d\n"
    "         %s\t            %d\n";

Вам нужны символы табуляции для выравнивания таблицы независимо от длины написанных в ней строк. Предполагается, что очереди всегда короткие. Например, код ниже:

printf(table, "11", 1, "222", 2, "33333", 3);

будет печатать:

bla-bla-bla bla-bla-bla bla-bla-bla bla-bla-bla
     bla-bla-bla       bla-bla-bla
         11                 1
         222                2
         33333              3

Ужасный совет N3. Вложенные макросы

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

Мои мысли на эту тему вы можете прочитать в следующей статье: Макрозло в коде C++.

Вы можете открыть ссылку в новой вкладке и сохранить ее на потом. Продолжайте читать эту книгу — к концу у вас будет целая коллекция вкладок с интересным материалом. Сохраните для дальнейшего чтения :).

Ужасный совет N4. Отключить предупреждения

Отключить предупреждения компилятора. Они отвлекают от работы и мешают писать компактный код.

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

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

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

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

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

Не знаю, что двигало разработчиком, но они просто отключили предупреждения в некоторых модулях. Возможно, они хотели сделать это временно, чтобы предупреждения не мешали исправлять ошибки компиляции. Но они забыли снова включить предупреждения. К сожалению, докопаться до истины не удалось — к тому времени этот человек уже не работал в компании.

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

Хороший совет

Старайтесь не получать никаких предупреждений при компиляции проекта. В противном случае вы увидите теорию разбитых окон в действии. Если вы постоянно видите одни и те же 10 предупреждений при компиляции кода, то вы не обратите внимание, когда появится 11-е. Если не вы, то ваши товарищи по команде напишут код, за который будут выдаваться предупреждения. Если вы считаете, что предупреждения — это нормально, то этот процесс скоро выйдет из-под контроля. Чем больше предупреждений выдается при компиляции, тем меньше внимания уделяется новым.

Более того, предупреждения постепенно станут бессмысленными. Если вы привыкли к тому, что компилятор постоянно выдает предупреждения, вы просто не заметите нового, который сообщит о настоящей ошибке во вновь написанном коде.

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

Конечно, не настаивайте на этом подходе слишком сильно:

Ужасный совет N5. Чем короче имя переменной, тем лучше

Используйте одну или две буквы для названия переменных. Таким образом, вы уместите более сложное выражение в одной строке экрана.

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

Другой способ запутать код — использовать аббревиатуры вместо обычных имен переменных. Пример: ArrayCapacity против AC.

В первом случае понятно, что переменная означает capacity — размер зарезервированной памяти в контейнере [1, 2]. Во втором случае вам придется угадать, что означает AC.

Следует ли всегда избегать коротких имен и сокращений? Нет. Будьте внимательны. Вы можете называть счетчики в циклах, например i, j, k. Это обычная практика, и любой разработчик понимает код с такими именами.

Иногда уместны сокращения. Например, в коде, реализующем численные методы, моделирование процессов и т. д. Код просто делает расчеты по определенным формулам, описанным в комментариях или документации. Если переменная в формуле называется SC0, то лучше использовать это имя в коде.

Например, вот объявление переменных в проекте COVID-19 CovidSim Model (я однажды проверил):

int n;             /**< number of people in cell */
int S, L, I, R, D; /**< S, L, I, R, D are numbers of Susceptible,
                    Latently infected, Infectious,
                    Recovered and Dead people in cell */

Вот как вы можете называть переменные. Комментарии описывают, что они означают. Такое наименование позволяет компактно записывать формулы:

Cells[i].S = Cells[i].n;
Cells[i].L = Cells[i].I = Cells[i].R = Cells[i].cumTC = Cells[i].D = 0;
Cells[i].infected = Cells[i].latent = Cells[i].susceptible + Cells[i].S;

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

Стив МакКоннелл приводит хорошие аргументы в пользу того, как называть переменные, классы и функции, в своей книге «Code Complete» (ISBN 978–0–7356–1967–8). Очень рекомендую прочитать.

Автор: Андрей Карпов. Электронная почта: karpov[@] viva64.com.

Более 15 лет опыта работы в области статического анализа кода и качества программного обеспечения. Автор большого количества статей о качестве кода C++. Microsoft MVP for Developer Technologies с 2011 по 2021 год. Андрей Карпов — один из основателей проекта PVS-Studio. Долгое время был техническим директором компании и разработчиком ядра анализатора C++. В настоящее время он в основном занимается управлением командой, обучением сотрудников и DevRel.

Полная версия книги по ссылке: 60 ужасных советов C++-разработчику.

Подпишитесь на ежемесячную рассылку, чтобы быть в курсе последних статей автора и его коллег в PVS-Studio.