Перед вами обновлённая коллекция вредных советов для C++ программистов, которая превратилась в целую электронную книгу. Всего их 60, и каждый сопровождается пояснением, почему на самом деле ему не стоит следовать. Всё будет одновременно и в шутку, и серьёзно. Как бы глупо ни смотрелся вредный совет, он не выдуман, а подсмотрен в реальном мире программирования.
Я буду публиковать советы по 5 штук, чтобы не утомить вас, так как мини-книга содержит много интересных отсылок на другие статьи, видео и т. д. Однако, если вам не терпится, здесь вы можете сразу перейти к её полному варианту: "60 антипаттернов для С++ программиста". В любом случае желаю приятного чтения.
Вредный совет N46. Тренируйся на работе для IOCCC
Пишите код так, как будто его будет читать председатель жюри IOCCC и он знает, где вы живёте (чтоб приехать и вручить вам приз).
Это отсылка к фразе "Пишите код так, как будто поддерживать его будет склонный к насилию психопат, который знает, где вы живёте". Фраза сказана Джоном Ф. Вудсом, но ошибочно приписывается Стивену Макконнеллу, так как он упоминает её в своей книге "Совершенный код".
Обыгрывая эту фразу, даётся вредный совет – писать как можно более необычный, странный и непонятный код, в духе конкурса IOCCC.
IOCCC (International Obfuscated C Code Contest) — конкурс программирования, в котором задачей участников является написание максимально запутанного кода на языке C с соблюдением ограничений на размер исходного кода.
Почему непонятный код — это плохо, кажется очевидным. Но всё-таки — почему? Программист большую часть времени тратит не на написание кода, а на его чтение. Я не могу вспомнить источник и точные цифры, но, кажется, там говорилось, что он проводит за чтением более 80% времени.
Соответственно, если код тяжело прочитать и понять — это сильно замедляет разработку. Отсюда же берёт своё начало идея всем в команде оформлять код единообразным способом, чтобы он был привычен для осознания.
Вредный совет N47. Развлекайся, оформляя код
Если в C++ переносы строк и отступы незначимые, то почему бы не оформить код в виде зайчика или белочки?
Странное развлечение, которое сделает код нечитаемым для тех, кто будет его поддерживать. Далее сошлюсь на пояснение к предыдущему совету, так как оно подходит и сюда.
Впрочем, не буду уж совсем снобом и ханжой. Я иногда писал творческие или юмористические комментарии, и не вижу в этом ничего плохого. Мы все люди, и испытываем эмоции. Вполне естественно, если мы иногда выплеснем их в комментариях. А все мы знаем, как сильны иногда эти самые эмоции при программировании :).
Главное не вредить самому коду, не перебарщивать, не оскорблять кого-то.
Встретив в коде дракона, я отнесусь к этому нормально. И буду благодарен, что автор обратил моё внимание на что-то таким необычным способом.
// .==. .==.
// //`^\\ //^`\\
// // ^ ^\(\__/)/^ ^^\\
// //^ ^^ ^/6 6\ ^^ ^ \\
// //^ ^^ ^/( .. )\^ ^ ^ \\
// // ^^ ^/\| v""v |/\^ ^ ^\\
// // ^^/\/ / `~~` \ \/\^ ^\\
// -----------------------------
Кстати, рекомендую поглазеть на разное необычное в статье моего коллеги "Комментарии в коде как вид искусства".
Вредный совет N48. У каждого свой стиль
Выравнивание и единый стиль не дают раскрыться вашей индивидуальности и креативности. Это притеснение свободы личности и самовыражения. Каждый должен оформлять код так, как ему нравится.
Ладно-ладно, два предыдущих совета были слишком эксцентричны. Что по поводу адекватного самовыражения в именовании сущностей и оформлении кода?
Этого не стоит делать по следующим причинам:
- Собственный код, написанный в привычном индивидуальном стиле проще читать и понимать. Вот только над проектами трудятся команды. В результате всем будет постоянно тяжело, так как придётся работать с кодом, написанным по-разному. Пустая трата времени и умственных усилий. Если выбрать один стиль, то вначале все тоже будут испытывать дискомфорт. Но постепенно к стилю привыкнут, и он станет удобным для чтения и написания. Повысится общая производительность команды.
- Конфликт стилей. Часто нужно поправить только какой-то фрагмент кода. Если каждый делает это на свой лад, то код будет выглядеть как лоскутное одеяло. Получается, что вообще нет никакого стиля, сплошная анархия. Если же переделывать чужой код, с которым приходится работать, под себя, то это будет отнимать много времени. С точки зрения автора первоначального кода, это вообще вредное действие. В общем, это ненужная почва для личностных конфликтов. Конечно, правя чужой код, можно подстраиваться под стиль, в котором он написан. Но, во-первых, это сложно, а во-вторых, ты всё равно точно не знаешь, какой именно стиль у твоего коллеги.
- То, что свой стиль нравится человеку, вовсе не значит, что он хороший. Есть множество приёмов, когда написание кода определённым образом помогает избежать ошибок. Некоторые из таких приёмов я описывал в мини-книге "Главный вопрос программирования, рефакторинга и всего такого". Поэтому полезно опытными членами команды совместно разработать и внедрить единый для всех стандарт кодирования.
Надеюсь, я был убедителен. Очень полезно в команде иметь стандарт кодирования. За основу можно взять, например Google C++ Style Guide или любой другой. Затем адаптировать для себя. Дополнительным источником для вдохновения могут стать:
- уже не раз упомянутая здесь книга "Совершенный код" С. Макконнелла (ISBN 978-5-7502-0064-1);
- Ален И. Голуб. "Веревка достаточной длины, чтобы… выстрелить себе в ногу." Мне лично она не очень понравилась, но я не могу про неё не вспомнить. Это классика;
- Standard C++ Foundation. FAQ: Coding Standards.
Со стилем программирования и стандартом кодирования связана тема форматирования кода. Если то, как именуются сущности, можно проверять на обзорах кода, то с форматированием могут помочь различные инструменты. Главное — договориться, как ваша команда форматирует код, а дальше — дело техники:
- clang-format;
- Best C++ Code Formatter/Beautifier;
- Are there any lint tools for C and C++ that check formatting?
- Is there any tool to standardize format of C++ code?
Вредный совет N49. Перегрузи всё
Перегрузите как можно больше операторов, в том числе и не арифметических, для как можно большего количества типов. Придавая операторам другой смысл, вы приближаетесь к созданию своего диалекта языка. Свой язык – это прикольно. А если ещё и макросы к делу подключить...
Перегрузка операторов — это более изящный вызов функций. Например, она позволяет использовать операторы + и = для строк. Это помогает писать изящный код конкатенации строк:
result = str1 + str2;
Согласитесь, это более красиво, чем:
result.assign(str1.strcat(str2));
Использовать перегрузку операторов следует только тогда, когда это упростит написание кода, как было показано в примере. Неуместное же использование, наоборот, затруднит чтение. Если действие перегруженных операторов интуитивно понятно, это хорошо. Если нет — это вредно.
Пусть A, B, C — это экземпляры класса матрицы. Тогда следующий код очевиден без пояснений:
A = B * C;
Другое дело:
A = B && C;
Этот код уже интуитивно не понятен. Конечно, можно посмотреть, что делает оператор && с матрицами. И, возможно, если это какая-то частая операция, действительно есть смысл использовать такую перегрузку для более компактного написания кода. Однако, я думаю вы понимаете, что, чем больше неочевидных действий выполняют перегруженные операторы, тем сложнее понимать код. Перегрузка начинает в этом случае усложнять код, а не упрощать.
P.S. Кстати, однажды Бьёрн Страуструп в качестве шутки предложил разрешить перегружать пробел: Generalizing Overloading for C++2000.
Вредный совет N50. Не верь в эффективность std::string
Универсальный std::string – это неэффективно. Можно ловчее и эффективнее использовать realloc, strlen, strncat и так далее.
То, что производительность программы можно существенно увеличить, отказавшись от класса std::string, является мифом. Но этот миф имеет под собой основание.
Дело в том, что раньше распространённые реализации std::string действительно оставляли желать лучшего. Так что, пожалуй, мы имеем дело даже не с мифом, а с устаревшей информацией.
Поделюсь собственным опытом. Начиная с 2006 года, мы разрабатываем статический анализатор PVS-Studio. В 2006 году он назывался Viva64, но это не имеет значения. Изначально в анализаторе мы широко использовали как раз стандартный класс std::string.
Шло время. Анализатор развивался, в нём появлялось всё больше диагностик, и с каждой версией он работал всё медленнее :). Пришло время задуматься над оптимизацией кода. Одним из узких мест, на которое указал профилировщик, оказалась работа со строками. И тогда настало время цитаты "в любом проекте рано или поздно появляется свой собственный класс строки". К сожалению, я не помню, ни откуда эта цитата, ни момент, когда именно это произошло. Кажется, это был 2008 или 2009 год.
Анализатор в процессе своей работы создаёт большое количество пустых или очень коротких строк. Мы написали свой собственный класс строки vstring, эффективно выделяющий память для таких строк. С точки зрения публичного интерфейса наш класс повторял std::string. Собственный класс строки повысил скорость работы анализатора приблизительно на 10%. Крутое достижение!
Этот класс строки служил нам долгие годы, пока на конференции C++ Russia 2017 я не побывал на докладе Антона Полухина "Как делать не надо: C++ велосипедостроение для профессионалов". В нём он рассказал, что уже много лет класс std::string хорошо оптимизирован. А те, кто использует свой собственный класс строки, являются ретроградами и динозаврами :).
Антон разобрал в своём докладе, какие оптимизации сейчас используются в классе std::string. Например, из самого простого – про конструктор перемещения. Меня же больше всего заинтересовала Small String Optimization.
Оставаться динозавром не хотелось. Мы провели эксперимент по обратному переходу с самодельного класса vstring к std::string. Для начала мы просто закомментировали класс vstring и написали using vstring = std::string;. Благо после этого потребовались незначительные правки кода в других местах, так как интерфейсы класса всё ещё почти полностью совпадали.
И как изменилось время работы? Никак! Это значит, что для нашего проекта универсальный std::string стал точно так же эффективен, как наш собственный специализированный класс, спроектированный нами с десяток лет назад. Здорово! Можно убрать один велосипед из кодовой базы проекта.
Однако мы говорили о классах. А во вредном совете предлагается спуститься на уровень функций языка C. Я сомневаюсь, что, используя эти функции, удастся написать более быстрый и надёжный код, чем в случае использования класса строки.
Во-первых, обработка С-строк (нуль-терминированных строк) провоцирует частое вычисление их длины. Не храня отдельно размер строк, сложно написать высокопроизводительный код. А если длина хранится, то мы вновь приближаемся к аналогу строкового класса.
Во-вторых, сложно написать надёжный код с использованием таких функций, как realloc, strncat и так далее. По опыту обзора ошибок в различных проектах можно констатировать, что код, использующий эти функции, прямо-таки "притягивает" к себе ошибки. Примеры ошибочных паттернов при использовании: strlen, strncat, realloc.
Об этой мини-книге
Автор: Карпов Андрей Николаевич. E-Mail: karpov [@] viva64.com.
Более 15 лет занимается темой статического анализа кода и качества программного обеспечения. Автор большого количества статей, посвящённых написанию качественного кода на языке C++. С 2011 по 2021 год удостаивался награды Microsoft MVP в номинации Developer Technologies. Один из основателей проекта PVS-Studio. Долгое время являлся CTO компании и занимался разработкой С++ ядра анализатора. Основная деятельность на данный момент — управление командами, обучение сотрудников и DevRel активность.
Ссылки на полный текст:
Подписывайтесь на ежемесячную рассылку, чтобы не пропустить другие публикации автора и его коллег.