Pull to refresh
340.58
PVS-Studio
Static Code Analysis for C, C++, C# and Java

60 антипаттернов для С++ программиста, часть 1 (совет 1 — 5)

Level of difficultyEasy
Reading time7 min
Views8.1K

1053_60_cpp_antipatterns_ru/image2.png


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


Я буду публиковать советы по 5 штук, чтобы не утомить вас, так как мини-книга содержит много интересных отсылок на другие статьи, видео и т. д. Однако, если вам не терпится, здесь вы можете сразу перейти к её полному варианту: "60 антипаттернов для С++ программиста". В любом случае желаю приятного чтения.


Вредный совет N1. Только C++


Настоящие программисты программируют только на C++!


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


Here is a list of systems, applications, and libraries that are completely or mostly written in C++. Naturally, this is not intended to be a complete list. In fact, I couldn't list a 1000th of all major C++ programs if I tried, and this list holds maybe 1000th of the ones I have heard of. It is a list of systems, applications, and libraries that a reader might have some familiarity with, that might give a novice an idea what is being done with C++, or that I simply thought "cool".

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


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


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


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


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


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


Вредный совет N2. Табуляция в строковых литералах


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


Речь идёт о строковых литералах, в которых требуется табуляцией отделять одни слова от других:


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

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


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


  1. На самом деле в литерале могут оказаться не табы, а пробелы. Это зависит от настроек редактора. Но выглядеть это будет так, как будто вставлена табуляция.
  2. Человеку, который будет сопровождать код, не будет сразу очевидно, используется в качестве разделителей табуляция или пробелы.
  3. Табуляция в процессе рефакторинга или использования утилит автоформатирования кода может превратиться в пробелы, что повлияет на результат работы программы.

Более того, однажды в реальном приложении я вообще видел приблизительно такой код:


  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-модуле и некоторых других полностью отключены предупреждения. Тогда я подключил старших коллег, чтобы провести расследование, как так вообще вышло.


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


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


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


Полезный совет


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


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


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


Конечно, это нужно делать без фанатизма и благоразумно:



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


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


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


Ещё один способ испортить код — это использовать аббревиатуры вместо нормального именования переменных. Пример: ArrayCapacity vs 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;

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


Хорошие рассуждения о том, как дать хорошие имена переменным, классам и функциям, есть в книге "Совершенный код" С. Макконнелла (ISBN 978-5-7502-0064-1). Всем её очень рекомендую.


Об этой мини-книге


Автор: Карпов Андрей Николаевич. E-Mail: karpov [@] viva64.com.


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


Ссылки на полный текст:



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

Tags:
Hubs:
Total votes 27: ↑20 and ↓7+13
Comments44

Articles

Information

Website
pvs-studio.com
Registered
Founded
2008
Employees
31–50 employees