LINUX.ORG.RU

60 антипаттернов для С++ программиста

 , , , ,


10

4

Постоянно писать «как делать правильный код» надоедает. Поэтому для разнообразия и развлечения написал мини-книгу «60 антипаттернов для С++ программиста». Этакие вредные советы в духе «Книга для непослушных детей и их родителей».

На самом деле там, не только вредные советы, но и разбор почему они собственно вредны. Будет полезно почитать новичкам в программировании. Думаю, каждый знает кого-то, кому будет полезно почитать этот материал :). Впрочем, опытные программисты тоже смогут найти интересное для себя и узнать/освежить знания про некоторых тонкие моменты C++.

Там много букв. Приглашаю запастись кофе/энергетиком и приступать. Буду рад обсуждениям и дополнениям, основанном на вашем опыте.

Ещё я этот текст переработал для бумажного издания. Оно в подготовке для печати. Смысл там в целом тот же, но пришлось многое переделать или расписать подробнее. Ведь нельзя в бумажной книге дать 100500 ссылок на сторонние ресурсы «читать здесь про xxx подробнее». Надеюсь, успеем напечатать к осенним конференциям и будем раздавать на стенде, например по кодовым словам. Приходите на стенд и говорите, что с linux.org.ru и что там на тему бумажной книги :)

Парочка вредных советов для примера:

  • Пишите ваши .h-файлы так, чтобы они зависели от других заголовков, и при этом не включайте их в свой заголовочный файл. Пусть тот, кто инклудит, догадается, какие заголовки нужно заранее заинклудить перед использованием вашего файла. Развлеките коллег квестами!
  • Пишите код так, как будто его будет читать председатель жюри IOCCC и он знает, где вы живёте (чтоб приехать и вручить вам приз).

P.S. PDF, если кому-то так удобнее.

Ответ на: комментарий от bugfixer

Здесь дело не в шашечках, а в том, чтобы как можно меньше думать об индексах и смещениях, концентрируясь на высокоуровневый сути операций, чему способствуют фичи C++11 и далее. Они выглядят более декларативно, минимизируют риски ошибок типа off-by-one.

seiken ★★★★★
()
Ответ на: комментарий от grem

Легко: выход из всех вложенных циклов наружу без нагромождения дополнительных «переменных для проверки нужно ли выйти наружу».

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

Loki13 ★★★★★
()
Ответ на: комментарий от Loki13

А если эти вложенные циклы и так составляют основную часть процедуры? Но да, смотреть нужно на конкретную реализацию.

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

grem ★★★★★
()
Ответ на: комментарий от grem

А если эти вложенные циклы и так составляют основную часть процедуры?

Ну так и будет эта основная часть в под-процедуре с выходом по return в основную, которая будет её вызывать. Тут больше проблема, если выходов из этих вложенных циклов много и нужно выходить в разные места, тогда проблема(хотя тоже можно возвращать разный код выхода и обрабатывать if then elseif else). Но это и с goto будет довольно некрасиво выглядеть и возможно стоит пересмотреть архитектуру.

Loki13 ★★★★★
()
Ответ на: комментарий от Loki13

Но это и с goto будет довольно некрасиво выглядеть и возможно стоит пересмотреть архитектуру.

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

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

PS: хотя я уже не могу вспомнить, когда я последний раз goto использовал.

soomrack ★★★★★
()
Последнее исправление: soomrack (всего исправлений: 1)
Ответ на: комментарий от grem

Легко: выход из всех вложенных циклов наружу без нагромождения дополнительных «переменных для проверки нужно ли выйти наружу».

Надо добавить в плюсы циклы с метками как в perl/fortran, чтобы можно было делать break LABEL и continue LABEL. Тогда goto можно будет задепрекейтить.

annulen ★★★★★
()
Ответ на: комментарий от max_lapshin

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

soomrack ★★★★★
()
Ответ на: комментарий от Andrey_Karpov_2020

Прочитал. Хорошо, интересно!

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

Mind your code!

soomrack ★★★★★
()
Ответ на: комментарий от max_lapshin

какие резоны то, прям интересно?

Самая банальная из возможных причин: необходимость линковки со старыми библиотеками, доступными только в бинарном виде. VC научился быть совместимым сами с собой только начиная с версии 2015.

annulen ★★★★★
()
Ответ на: комментарий от soomrack

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

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

annulen ★★★★★
()
Ответ на: комментарий от max_lapshin

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

AntonI ★★★★★
()
Ответ на: комментарий от seiken

Здесь дело не в шашечках, а в том, чтобы как можно меньше думать об индексах и смещениях, концентрируясь на высокоуровневый сути операций, чему способствуют фичи C++11 и далее. Они выглядят более декларативно, минимизируют риски ошибок типа off-by-one.

Полностью согласен. In general. Но - приоритеты бывают разные: если я могу несколько тактов сэкономить - я буду экономить. И я думаю что вот этот вот лупчик обогнать Вам не удастся (эту форму мне, кстати, один из gcc dev’ов подсказал). Главное следовать правилу «write what you know, know what you are writing» (c) не мой.

bugfixer ★★★★★
()
Ответ на: комментарий от AntonI

у некоторых весьма специфических заказчиков допускается только софт прошедший весьма специфическую сертификацию.

Всё проще: даже outside of гос организаций и ОПК смена компилятора (или версии оного) - это всегда риски. Риски regressions. И хорошо если только perf regressions. И «не всё то солнышко что встаёт». Больше скажу - по моему глубокому убеждению смена компилятора только ради того чтобы оставаться на «bleeding edge of technology» - довольно бессмысленна: надо чётко отдавать себе отчёт «зачем».

bugfixer ★★★★★
()
Последнее исправление: bugfixer (всего исправлений: 1)
Ответ на: комментарий от bugfixer

надо чётко отдавать себе отчёт «зачем».

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

Сегодня сборка того, что ты делаешь, любым новым компилятором — это просто ещё одно правило в CI.

А если горе-программистишко до сих пор не придумал, как организовывать канареечный деплой, то уж точно пора научиться.

А вот «зачем» — всё предельно ясно. Чтобы потом, когда прийдется одним днем переезжать на другой компилятор, или с винды (за которую ты теперь получишь заслуженный тюремный срок) на линукс, это можно было сделать в какие-то обозримые сроки.

max_lapshin ★★★★★
()
Ответ на: комментарий от AntonI

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

max_lapshin ★★★★★
()
Ответ на: комментарий от max_lapshin

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

Но бумажка, емнип (мопед не мой) даётся на конкретный бинарник с конкретным хэшем.

AntonI ★★★★★
()
Ответ на: комментарий от fsb4000

Нет ни одного заказчика кто будет пользоваться неподдерживаемым софтом.

Ахаха!

Нет, погодите, вы серьезно?

АХАХАХА!

Да практически каждый первый, у кого систему писали не смузихлебы вчера имеет легаси, начиная с 13й слаки и кончая компилятором из нулевых. Просто потому что железо на 2000+ точках обновить будет стоить сколько компания за 50 лет не заработает. Распределенные системы контроля, серверы, банкоматы, камеры, системы слежения, «умные светофоры». Что у заказчика есть, на том и будете писать.

PPP328 ★★★★★
()
Ответ на: комментарий от Loki13

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

PPP328 ★★★★★
()
Ответ на: комментарий от annulen

Не согласен. Как пример:


/* 55 строк комментариев, рассказывающих про атмосферное давление на определенной высоте, о его влиянии на температуру кипения, о контроллере, который имеет баг на первом бите, если текущее давление ниже атмосферного на 10% */

register_pressure |= 1;
PPP328 ★★★★★
()
Ответ на: комментарий от PPP328

Если бы не 55 строк, а покороче, то это был бы пример хорошего комментария, так как информация в нём является справочной и не устареет. А если бы это было 55-строчное описание реализации, которая следует дальше, то оно с большой вероятностью устарело бы после нескольких правок.

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

annulen ★★★★★
()
Ответ на: комментарий от AntonI

Я в таких случаях обмазывал содержимое условной компиляцией, чтобы и код, использующий новые фичи не выкидывать - а они могут работать быстрее -, и на старых компиляторах уровня gcc 4.7 всё собиралось.

grem ★★★★★
()
Ответ на: комментарий от annulen

Ну вот у меня есть вот такой пример с работы: https://i.postimg.cc/HWcfrnDS/2023-06-23-12-50-50.png описание со схемами идет до 154й строки. Потом идет модуль на 150 строк, из которых 50 - это семантически выделенные однострочные static функции типа

/*! \brief Возвращает массив со списком расписаний из запроса
 * \param[in] request Запрос
 * \return Массив с расписаниями */
static struct frs_json * _schedule_list(struct frs_json * request) {
    static const char * path_schedule = "schedule";
    return frs_json_find(request, path_schedule, FRS_JSON_ARRAY);
}

Плохо штоль? Хорошо ж!

PPP328 ★★★★★
()
Ответ на: комментарий от PPP328

Конкретно такой стиль написания документирующих комментариев плох тем, что при изменении списка параметров функции документация к параметрам из тела комментария не изменяется (часто забывают это делать). Лучше бы было так (тем более, что Doxygen это позволяет):

/*! \brief Получение списка расписаний из запроса
 * \return Массив с расписаниями */
static struct frs_json * _schedule_list(
    //! Запрос для которого нужно получить расписания.
    struct frs_json * request)
{
    static const char * path_schedule = "schedule";
    return frs_json_find(request, path_schedule, FRS_JSON_ARRAY);
}

Ну и в комментарии нужно бы еще указать кто и как отвечает за дальнейшую судьбу возвращенного указателя (нужно ли его удалять вручную через free или какую-то другую функцию, или же это указатель на один и тот же объект, который перезаписывается при каждом вызове _schedule_list). А так же является ли NULL легальным значением для возвращенного значения.

И если уж разбирать приведенный пример еще дальше, то название «schedule_list» плохо соответствует смыслу «получение массива расписаний для запроса». Может быть лучше было бы «find_schedule_list_for_request».

eao197 ★★★★★
()
Ответ на: комментарий от annulen
	/*
	  .      / a1 b1 \     
	  .     / / | | \ \    
	  .   a2 /  |t|  \ b2   
	  .   / /   |h|   \ \   
	  . eA ( tA |i| tB ) eB 
	  .   \ \   |s|   / /   
	  .   a1 \  | |  / b1   
	  .     \ \ | | / /     
	  .      \ a2 b2 /      
	*/	

вот такие комменты иногда пишу;-)

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

AntonI ★★★★★
()
Ответ на: комментарий от grem

Я тоже обмазывал, пока не стал в ifdef-ах путаться. В итоге пришел к тому, что выношу специфичные для компилятора реализации каких то фич в отдельные хидеры.

Скажем под VS2008 не нашел нормального способа сделать параллельный генератор случайных чисел. Пришлось че то велосипедить методом копипасты, да еще и следя за лицензией…

AntonI ★★★★★
()
Ответ на: комментарий от PPP328

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

Ну разные конечно случаи бывают, но чаще эти переменные и свойства или в структуре лежат, которую одной ссылкой прокинуть можно или уже члены класса(мы же про С++ говорим?). И с чего бы вызов стал особо занимать при прокидывании по ссылке?

Я конечно видел математические расчеты с сотней переменных(с названиями i,j,k,ss,ll и т.д.) и функциями на 5 экранов. Такое иногда математики пишут, не имеющие особо навыков в программировании. Но такое даже говнокодом не назвать, говнокод может обидется.

Loki13 ★★★★★
()
Последнее исправление: Loki13 (всего исправлений: 1)
Ответ на: комментарий от eao197

кто и как отвечает за дальнейшую судьбу возвращенного указателя

У нас есть стайлгайд, в котором сказано что нужно писать слова «Аллоцированный указатель», если там что-то надо удалять

find_schedule_list_for_request

Длинно и не совпадает со стайлгайдом: [Префикс проекта]_<objectname>_<objectproperty/method>

PPP328 ★★★★★
()
Ответ на: комментарий от AntonI

вот такие комменты иногда пишу;-)

 * ~~~
 * ┌────────────────────────────────────────┐
 * │ /org/freedesktop/UDisks2/block_devices │
 * └─┬──────────────────────────────────────┘
 *   │ ┌────────────────┐ ┌─────────────────────────────────────┐
 *   ├─┤ sda            │>│ org.freedesktop.UDisks2.Block Drive │
 *   │ └────────────────┘ └─┬───────────────────────────────────┘
 *  ...                     │ ┌──────────────────────────────────────────────────────────────────────────────────┐
 *                          └─┤ variant.objectpath = /org/freedesktop/UDisks2/drives/WDC_WD2005FBZ_01YCBB2J0SU2D │
 *                            └──────────────────────────────────────────────────────────────────────────────────┘
 * ~~~ */

:D

PPP328 ★★★★★
()
Последнее исправление: PPP328 (всего исправлений: 1)
Ответ на: комментарий от max_lapshin

это просто смешно.

Давайте вместе посмеёмся.

Сегодня сборка того, что ты делаешь, любым новым компилятором — это просто ещё одно правило в CI.

И дальше? Ну вот подождали мы несколько лишних часов и оно таки собралось вашим любимым новомодным компилятором, и даже все unit tests проходят. Вы будете мне рассказывать что этого достаточно чтобы быть уверенным что ничего не рванёт в проде? Нет - пока это не выкатилось в прод и не поработало некоторое время под нагрузками ни в чём быть уверенным нельзя. А рисковать продом только ради того чтобы попробовать новый компилятор никто в здравом уме не будет. Нужны более веские причины. Например: «у нас есть основания полагать что сборка новым компилятором на X% быстрее». Только я почему-то до сих пор такий «волшебных» ускорений не видел. А вот regressions - видел. Как из-за наших багов (UB), так и из-за багов компиляторов.

А если горе-программистишко

Без комментариев.

как организовывать канареечный деплой,

Я не знаю что такое «канареечный деплой». Зато я знаю что мне придётся выделить десяток - другой машинок чтобы организовать минимальную песочницу способную справляться с боевыми нагрузками и покрывающую все ключевые модули. И это не ваши подкроватные сервера, а вполне себе боевые машинки по $20-40k каждая. Плюс место в DC, плюс питание / охлаждение / connectivity / etc. Кмк, таким ресурсам можно найти лучшее применение.

А вот «зачем» — всё предельно ясно. Чтобы потом, когда прийдется одним днем переезжать на другой компилятор, или с винды

Бинго! Вот оно! Ваши способности к планированию вызывают у меня серьёзные опасения. Посему - продолжайте играться в свои бирюльки, и дайте уже взрослым дядькам «работу работать».

bugfixer ★★★★★
()
Ответ на: комментарий от PPP328

Да практически каждый первый, у кого систему писали не смузихлебы вчера имеет легаси, начиная с 13й слаки и кончая компилятором из нулевых.

В Microsoft одни смузихлёбы?

Windows собирается Visual Studio 2022.

Там специальный процесс на это есть «NT build».

Даже если брать клиентов, они обновляются когда что-то становится EOL, потому что это безумие в современном мире сидеть на дырявом софте.

Это либо бомжи у которых нечего украсть, либо уже банкроты.

Безумно думать что где-то есть прод на какой-нибудь Ubuntu 8.04 и gcc 4.2, в котором есть данные/деньги.

Их уже взломали и обчистили.

fsb4000 ★★★★★
()
Последнее исправление: fsb4000 (всего исправлений: 1)
Ответ на: комментарий от fsb4000

Безумно думать что где-то есть прод на какой-нибудь Ubuntu 8.04 и gcc 4.2, в котором есть данные/деньги. Их уже взломали и обчистили.

Работаю на компанию у которой есть Slackware 13. Никто не умер. На эти железки что-то новее не лезет.

PPP328 ★★★★★
()
Ответ на: комментарий от fsb4000

Безумно думать что где-то есть прод на какой-нибудь Ubuntu 8.04 и gcc 4.2, в котором есть данные/деньги.

В фирме, где я раньше работал, использовался кросс-компилятор gcc 2.95 для некоторых железок.

annulen ★★★★★
()
Ответ на: комментарий от annulen

В фирме, где я раньше работал, использовался кросс-компилятор gcc 2.95 для некоторых железок.

это фигня.. вот запускать bc3.11 из dosbox чтобы собрать 16-бит прошивку, вот то дело.. :-) PLC это надёжно и сцуко вечно

MKuznetsov ★★★★★
()
Ответ на: комментарий от bugfixer

Я не знаю что такое «канареечный деплой».

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

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

max_lapshin ★★★★★
()
Ответ на: комментарий от max_lapshin

ну значит просто твои примитивные знания о программировании остались где-то в очень замшелой древности

Почитал. По другому это называется gradual rollout, и мы это практикуем уже больше 30 лет. Каждый день. И то что народ называет extreme programming мы тоже практикуем. Число только моих релизов в день легко достигает 10. Так что позволю себе утверждать что пару вещей про деплоймент я знаю, и с большой вероятностью опыта имею не меньше вашего.

ты просто обманываешь тех, кто тебе платит деньги и пускаешь им пыль в глаза.

Что вы знаете о том кто платит мне деньги? Софт пишется для внутреннего потребления и деплоится на наши сервера в наши датацентры. Мне некого обманывать, как раз наоборот - так как мы сами себе заказчики мы не переносим риски на клиентов и не используем их как подопытных кроликов.

как и остальной бред который ты несешь дальше.

И всё что у тебя есть — неуемное самомнение о том, что ты якобы взрослый, который «знает как надо»

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

bugfixer ★★★★★
()