Pull to refresh

Comments 164

Правило 1 - "короткие функции" - и правило 20 - "один return" - два антипаттерна, которые существуют лишь для поддержки друг друга.

Нет, ни в коем случае не старайтесь следовать правила "одного return". Наоборот, при написании функции старайтесь отсечь простейшие случаи сразу, обработать их в начале функции и тут же сделать явный return. Это существенно повышает удобочитаемость кода, так как явно дает читатель возможность понять, что обработка полностью закончена и больше ничего делать не нужно (т.е. ниже по коду функции больше ничего нет). Придерживайтесь этого правила и при написании циклов: при описании итерации старайтесь отсечь простейшие случаи сразу, обработать их в начале итерации и тут же сделать явный continue.

Стратегия "одного return" не дает понять, закончена обработка случая или нет. Чтобы побороть эту проблему сторонники "одного return" придумали правило "коротких функций". Это чушь. Функция может быть сколько угодно длинной, хоть на 100 экранов. Ничего плохого в этом нет. Функция должна реализовывать некую законченную хорошо очерченную абстракцию - это важно. А сколько экранов займет реализация это абстракции - не имеет никакого значения. Ни в коем случае не подразбивайте функции на под-функции только из соображений длины - введение притянутых за уши искусственных под-абстракций существенно затруднит чтение и понимание кода.

12–Если что-то можно проверить на этапе компиляции, то это надо проверить на этапе компиляции (assert(ы))

O_o? Может имелись в виду static_assert(ы)? assert(ы) - это средство проверки времени выполнения.

32–Функции CamelCase переменные snake_case

Термином camel case называют капитализацию с в стиле camelCase.CamelCase - это не camel case.

38--При сравнении переменных с константой константу ставьте слева от оператора ==.

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

39–В каждом if всегда обрабатывать else вариант даже если else тривиальный. Это позволит предупредить многие осечки в программе.

Еще одно фейковое правило из той же оперы, что и 38. Звучит логично, но все уже давно известно в жизни подобных проблем не возникает. Они существуют только в "рыбацких рассказов" от чайников.

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

В таком виде - "всегда" - очень вредный антипаттерн. Занимаясь dummy-инициалиазцией локальных переменных вы лишь подавляете полезную функциональность санитайзеров кода, т.е. заметаете под ковер потенциальные ошибки.

Инициализация - полезнейшее свойство языка, которое нужно использовать всегда, когда это возможно. А именно: когда у вас есть наготове полезное инициализирующее значение. Если такого значения у вас нет на момент объявления переменной, то лучше оставить ее неинициализированной, чем инициализировать ее "чем попало".

42–В хорошем С-коде в принципе не должно быть комментариев. Лучший комментарий к коду - это имена функций и переменных. 

Это - странная максима.

Во-первых, лучший комментарий к коду - это огромное количество assert, описывающих подразумеваемые автором кода инварианты.

Во-вторых, без текстовых комментариев не обойтись.

Во-вторых, без текстовых комментариев не обойтись.

Да, не обойтись.

Еще одно фейковое правило из той же оперы, что и 38. Звучит логично, но все уже давно известно в жизни подобных проблем не возникает. Они существуют только в "рыбацких рассказов" от чачайников.

А вот тут поподробнее. Я всегда использую else на случай если в коде появится дополнительный тип константы который нужно обработать в else if

О ужас! Yoda conditions... Катастрофическая чушь, которую уже давно отправили на свалку истории

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

А зачем? Я вот люблю Yoda conditions в некоторых случаях. Удобнее читать. Например, когда из контекста сразу понятно какая переменная будет ща проверяться 3-5 раз на всякие значения, то глаз сразу целпяется за то значение, которое ожидается.

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

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

Множественные return хороши в C++ в чистом C, правило одного return имеет смысл, потому что перед ним как правило стоит код освобождения ресурсов и метка для goto. Соответственно везде где вы отсекаете на раннем этапе случаи вы вместо return пишите что-то типа goto free_resources_and_return, которая приведет нас к корректному освобождению памяти и к единственному return. Это, кстати, еще отличный практический пример, когда goto - не зло.

Немедленный return или же немедленное goto, где будет return? Кажется мне, или это контекстуальные синонимы?

Разница в освобождении ресурсов перед `return`.

Вы меня не поняли. Убирать за собой - хорошо. Но у меня вопрос к логике кода. И вот с точки зрения логики разницы между `if (something) return;` и `if (something) goto deinit;` совершенно ноль. Автор же предлагает писать кучу вложенных if-ов, после которых идет один return.

Множественные return хороши в C++ в чистом C, правило одного return имеет смысл, потому что перед ним как правило стоит код освобождения ресурсов и метка для goto.

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

Правда, у такого стиля есть

свои недостатки

Например, у меня довольно часто функция настраивает свой контекст, потом открывает диалоговое окно и задает какие-то вопросы, потом работает с файлом и т.д. Причем брейк может из любого места случиться. Потому перед return может быть не одна метка, а две или даже три. Тут уже вопрос, а стоит ли так писать, или лучше структурно? Для себя я решаю так: пока переходы goto не "перекрещиваются", это терпимо, например:

...
goto 900
...
goto 800
....
800   ...
900   ...

А вот переход goto 900 после оператора goto 800 - это уже было бы некузяво, надо функцию декомпозировать.

Ага. А ещё такие правила рождают монстров вида:

do {
  ...
  if (condition) break;
  ...
} while (false);

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

Это, кстати, еще отличный практический пример, когда goto — не зло.

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

Я здесь крокодилмимо (на С не пишу и язык не знаю), поэтому читал, примеряя все правила к своему языку. По ходу отмечал те пункты, которые к фортрану не очень подходят.... А потом открыл комментарий heCalligrapher и внезапно увидел, что почти все критикуемые им пункты (кроме 12 и 32),

уже присутствуют в списке моих пометок

Полностью применимы: 2-7, 13-15, 17-19, 22-23, 26-30, 35-37... Остальное (из не прокомментированного heCalligrapher), в основном, относится к специфике языка. Например, запись вида if (x = 10) ... у нас даст ошибку компиляции, поэтому писать что-то типа if (10 = x) ... никому и в голову не придет ;-) А за пп.33 отвечает язык (повторный USE игнорируется, что очень удобно при множественных ссылках на один модуль из других включаемых модулей). А вот к пп.17: небольшая добавка: если это не повлечет заметных накладных расходов, то лучше ВСЕ входные параметры функции проверять, а не только указатели. Ну и лично я стараюсь начинать файл с высокоуровневых функций, - мне проще иди от общего к частному. Если же частного слишком много, оно выноситься в отдельный модуль.

Если убрать специфику конкретного языка, то правила "хорошего тона" будут почти одинаковы для всех языков одной группы ;-)

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

Комментарии к коду иногда нужны. Я жёстко поругался с ревьюерами кода, когда они слепо следовали этому принципу и запороли мой кодревью. Там был код совмещения API двух крупных библиотек - Windows API и Qt, там реально были команды, выглядевшие как шаманские заклинания. Их нужно объяснять развёрнутым комментарием. Создавать функцию, содержащую 50 слов комментария в своём названии, следуя формальному методу, выглядит нелепо. В наше время ещё код или его идея часто берутся из сети - в таких случаях ссылка на источник тоже, естественно, записывается в коммент.

Имхо правило одного return-а введено в первую очередь для реализации ручного unwinding-а.

А есть early return pattern, достоинствами которого автор почему-то не хочет пользоваться.

Согласен, хорошо знать английские названия соответствующих идей.

Функция может быть сколько угодно длинной, хоть на 100 экранов.

Большие функции как раз очень трудно читать, отлаживать, тестировать и, самое главное, поддерживать.

1–Все функции должны быть менее 45 строк. Так каждая функция сможет уместиться на одном экране. Это позволит легко анализировать алгоритм и управлять модульность.

И сразу вспмнился код Minix-а, который попал мне в руки в далёком 1987 году вместе с книгой Эндрю Таненбаум «Operating Systems: Design and Implementation» (1987, ISBN 0-13-637406-9). Фактически мы только осваивали С-код. Оказалось. что код самого Minix-а читался очень легко — автор следовал этому совету 45-ти строк. Более того там встречались и пустые функции. наверное. как задел на будущее или в них отпала нужда. В этой же книге всего на дюжине страниц был описан и сам Си.

Мы до сих пор не привыкли к современной практике «читаемости» кода, когда перед функцией несколько строк ее описания для «понятности», между блоками функции достаточно часто пробелы для «читаемости», фигурные скобки блоков не только всегда присутствуют, но вынесены на отдельные строчки для «удобства» и так далее.
В результате какой-нибудь короткий код, который в норме занимает строк 30 — растягивается на 90-120 строк, просто для «понятности и читаемости» и на один экран его уже поместить без шансов.
Нет, мы, конечно, приучили себя писать именно так, но каждый раз это когнитивный диссонанс и насилие над собой:)
мы, конечно, приучили себя писать именно так, но каждый раз это когнитивный диссонанс и насилие над собой:)

И в итоге, в конце видишь, что все попытки следовать этим правилам — провалились. Привести код в порядок откладываешь на потом...

>. Коды возврата приходится анализировать отладчиком до проверки условия.

А может отладчик нормальный завести?

А может отладчик нормальный завести?

Ну вот у меня отладчик VS, и я не умею в нем поставить точку останова на вызове конкретной функции (например check_condition2() или на <Оператор_1> внутри строки:

if (check_condition1(...) .and. check_condition2(...)) then; <Оператор_1>; else; <Оператор_2>; end if
Поэтому для отладки мне удобнее написать:
condition1_is_true=check_condition1(...)
condition2_is_true=check_condition2(...)
if (condition1_is_true .and. condition2_is_true) then
  <Оператор_1> 
else 
  <Оператор_2>
end if

Может, конечно, это только в фортране такая специфика... или надо на более последнюю версию VS перейти?

Чего люди только не сделают, лишь бы в EAX не заглядывать)

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

а что такое заглядывание в eax?

Тоже интересно, что еще за акроним такой EAX ? Я 10 лет в разработке и еще ни разу не слышал никакого EAX.

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

UFO just landed and posted this here

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

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

UFO just landed and posted this here

В Linux особо много кода на С уже и не пишут. В основном редактируют деревья устройств на языке DeviceTree. A в User space уже давно торжествует Python.

17–Если функция получает указатель, то пусть сразу проверяет на нуль значение указателя. Так прошивки не будут падать при получение нулевых указателей.

На 32-битных системах нужно ещё проверять и на максимальное значение. Например, у нас есть функция, отсчитывающая секунды за счёт выполнения 100 раз 10-миллисекундного цикла. А ей на вход по ошибке подадут 4 миллиарда.

Описанный Вами пример не про указатели, это скорее про значения переменных.

в качестве таймаута кстати часто передают 0xFFFFFFFF (== -1), что означает "бесконечный таймаут". Если не проверять, то по сути закладывается бомба на то что "бесконечный" таймаут через приличное количество времени все-таки истечет.

Такая конструкция 

if (val = 10 ) doSmth=1;

незаметно собирается и вызовет трагедию во время исполнения.

gcc -Wall -Wextra -pedantic -Werror хотя бы по умолчанию. Хотя -pedantic по желанию.

-pedantic обязателен, если вы пишете именно на С, а не на развеселом GCC-шном студенческом суржике.

-pedantic-errors - лучше, но уже по желанию

-Werror - это что-то непонятно зачем нужное.

-pedantic не всегда дружит со старым кодом. -Werror это для того, чтобы было не лень пофиксить предупреждения, или просто их не пропустить.

Старый код - это особый случай, к теме не относящийся. Да и правильнее сказать (уж не стесняясь), что не дружит не со "старым кодом", а с безграмотным кодом.

Спасибо за статью. Я хоть и не С-ишнтк, но вычерпнул для себя несколько правил. Правда без вопросов не остался.

Получается, что > и >= это вообще два бессмысленных оператора языке С.

Неужели данные операторы настолько бесполезны? Мне казалось, что мы сначала обращаем внимание на сам оператор, а потом на операнды.

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

Тоже не понял про > и >= . Мне думается слева лучше ставить "основную" по смыслу кода переменную, так сразу видно что нужно сравнить, а справа уже с чем сравнивать. Такой порядок анализа более естественнен .

Это вообще какой то бред.

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

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

UFO just landed and posted this here

Не все здесь конкретно про С. Некоторые вещи про стиль и удобство, а некоторые надуманы.

UFO just landed and posted this here

Ну к примеру

32 - CamelCase/snake_case стиль вполне используемо за пределами C

37- Использование автоматических форматировщиков отступов. Идея хорошая. Во многих IDE это встроенная опция.

38 - IF, он и во многих прочих языках IF. C - подобный синтаксис много где еще используется. /хотя я с 38 не согласен/

39 - if & else / Тоже не только про С

UFO just landed and posted this here

Но тем не менее, оно не только про "Читая такие статьи понимаешь — как же хорошо не быть программистом на С. " ?

UFO just landed and posted this here

Какие бы вы пункты назвали универсальными за пределами С?

Ничего не могу сказать про универсальность, но, к примеру, в фортране вполне применимы 2-7, 13-15, 17-19, 22-23, 26-30, 35-37. Язык, конечно, не самый популярный сейчас, но в своей нише весьма конкурентоспособный

Что сейчас пишут на Fortarn(е)?

В той сфере, где я работаю - пишут программы для научных расчетов. Причем не только "одноразовки" для личного пользования (когда выбор языка определяется преимущественно тем, на чем учили программировать в ВУЗе), но и "перемалыватели чисел", так как по производительности на таких задачах современный фортран вполне себе держится в лидерах, а по простоте и удобству кодирования именно вычислений многих соперников превосходит. Насколько я знаю от коллег, во многих научных организациях (и не только в РФ) это до сих пор либо выбор номер 1, либо близко к тому.

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

Практически все эти правила, MISRA уж точно, "написаны кровью", когда корявый нечитаемый код вешает МК в какой-нибудь индустриальной системе или автомобиле. Так что в эмбеде строгое соблюдение правил очень важно.

UFO just landed and posted this here

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

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

UFO just landed and posted this here
UFO just landed and posted this here
UFO just landed and posted this here

JavaScript = "мне без разницы на каком языке Вы приведёте пример кода..." Не хотите соблюдать правила, не соблюдайте...

Я думаю речь о том, что в обработчике прерывания не стоит делать ни сложной математики, ни тем более записи в еепром, т.к. это будет означать что другие прерывания в это время не будут обрабатываться (или будут, но только более высокого приоритета, что тоже некруто). Поэтому правило в том чтобы в прерывании только выставлять флаги, а тяжелую обработку делать в основном цикле\задачах rtos и оно не зависит от языка - хоть раст хоть js.

Хотя конкретно в этом посте такого правила нет, так что диалог действительно похож на недопонимание.

Спасибо. Вот не умею я доходчиво и развёрнуто донести до собеседника свои мысли. Беда у меня с этим 😊😊😊

Есть такие понятия как верхняя и нижняя половины обработчика прерываний.

Верхняя половина работает в ISR ставит флаги

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

Тогда проще написать машину состояний или вообще RTOS, чем плодить циклы и суперциклы и всё это на проце с вытесняющими прерываниями.

Написали же. Верхние и нижние обработчики это термин из ядра Linux.

Многие правила очень даже подходят для других языков.

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

UFO just landed and posted this here

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

Вот автор в статье дает рекоммендации по правилам.

Как именно правила енфорсить - это уже за рамками изначальной статьи.

Варианты есть разные:

  • Гит хук

  • Статический анализатор в ИДЕ

  • Серверный линтер

  • ПР ревью для сложных правил

  • ???

1–Все функции должны быть менее 45 строк.

Интересно, как разбивать многовариантный switch по 45 строк...

Ну как как...

switch(i){
  case 1: ... break;
  ...
  case 39:... break;
  default: funcSwitch4050(...); break;
}

switch это не функция, а макрос.

Когда Switch разрастается больше 45 строк, то надо делать статические LookUpTable(лы) (LUTы). Элементом LUT(а) может являться указатель на функцию до 45 строк.

static const AdcChannelInfo_t AdcChannelInfoLut[] = {
    {.code = ADC_CHANNEL_0, .adc_channel = ADC_CHAN_0},  
    {.code = ADC_CHANNEL_1, .adc_channel = ADC_CHAN_1},
       ....
    {.code = ADC_CHANNEL_999, .adc_channel = ADC_CHAN_999},
};

uint32_t AdcChannel2HalChan(AdcChannel_t adc_channel) {
    uint32_t code = 0;
    uint32_t i = 0;
    for(i = 0; i < ARRAY_SIZE(AdcChannelInfoLut); i++) {
        if(AdcChannelInfoLut[i].adc_channel == adc_channel) {
            code = AdcChannelInfoLut[i].code;
            break;
        }
    }
    return code;
}

UFO just landed and posted this here

Лучше потому что функции помещаются на один экран.
А для поиска по коду можно обратиться к утилите grep.

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

switch(i)
{
case 1: 
  state1();
  break;
...
case N:
  stateN();
  break;
}

Странно что не упомянули о том что за if, for ... всегда должны быть { }

27–Если код не используется, то этот код не должен собираться. Это уменьшит размер артефактов

Сомнительно. Если, к примеру, функция не вызывается, то её в итоговом бинарнике не будет.

Только в тривиальных случаях или если у вас работает LTCG. Иначе линкер притащит объектный файл целиком. Попробуйте сделать статическую либу и вызвать единственную функцию из обьектника, входящего в нее.

Ещё одна мера противодействия - размещение каждой функции в выделенной ей секции. Тогда линкер сумеет эти секции убрать.

на эту тему была книга "Правила программирования на Си и Си++. Ален И. Голуб"

Одна из лучших, если не самая лучшая, книга о программировании из всех, что я прочитал. Не только о Си и плюсах, там много очень глобальных вещей, в том числе о подходе к разработке. Никакой воды, всё из практики, всё с объяснением почему и как. Недавно перечитывал, и в очередной раз поражался, насколько вещи, там описанные, актуальны до сих пор. Большинство граблей, на которые наступают коллеги по работе, там расписаны. Так что пытаюсь по мере сил рекламировать в коллективе сию книжку.

В стародавние времена "подпрограмму" создавали в тех случаях, когда код используется более одного раза. Сейчас же всё перевернули с ног на голову - давайте разобьём код на стопицот функций, которые затем вызываются ровно в одном месте и нигде более. В итоге разбухшие заголовочные файлы либо декларации функций в начале C-файла, т.е. функция объявляется и описывается кодом в двух разных местах. А ведь ещё надо придумать несущие смысл названия, названия которых вылетают из головы как только исчезли с экрана после скроллинга. И тут копипаста - наше всё. Через некоторое время начинаешь рыскать по коду в надежде понять, что же эта функция в 45 строк реально делает. А если ещё создавать документацию в DoxyGen... Переизбыток функций не добавляет понимания.

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

Но ведь цель функции -- это не только переиспользование кода

Она выполняет ещё одну важнейшую вещь: даёт осмысленное название куску кода

Что позволяет лучше понимать код без комментариев =)

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

Разбухшие заголовочные файлы говорят о ужасной разбивке кода на модули.

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

К тому же всё в одной функции делать зачастую бессмысленно. Либо компилятор сам заинлайнит либо можно заставить.

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

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

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

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

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

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

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

Что касается моего первого тезиса (код используется более одного раза) это отсылка к истории 70-90-х про медленные процессоры, фортран, ассемблер и всё такое.

PS: минусанул точно не я

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

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

А кто гарантирует, что он будет действительно фиксом для "всего кода"?

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

Когда эта ситуация обнаруживается, как правило начинается дополнительный "фиксинг" путем введения "странных параметров" для данной функции. Это параметры, которые не несут никакой осмысленной абстрактной семантики, а служат лишь для идентификации места, из которого вызвана функция. Им, как правило, дают какие-то ничего не говорящие странные имена, вроде mode, passили for_johnny.

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

UFO just landed and posted this here

Есть две принципиально разные ситуации повторения кода.

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

  2. Код повторяется системно - эволюция будет только синхронно.

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

ключевое слово, "одинаковый",

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

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

Немного синтетическая иллюстрация:

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

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

я вас понимаю и согласен с вашим примером, такое случается.

Но в этом случае уже код не идентичен и это уже совершено другая функция.

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

Из области не правил, но оптимизации. Если есть цикл от 0 до N и не важно в каком направлении его выполнять, то лучше двигаться не от 0, а к 0, т.к. на многих архитектурах сравнение с константой и сравнение с 0 - сильно различны по размеру команды и/или скорости выполнения (при условии возможности оптимизации компилятора). Например для x86 (32 бита):

cmp eax,5

и

test eax,eax
Часто второе оптимальнее даже для аппаратно независимых языков типа Java.

UFO just landed and posted this here

Какие тут бенчмарки ))) Большой экономии по размеру не будет, т.к. в цикле 0 -> 5 идёт сравнение с константой "5" (для хранения которой 4 байта на константу), зато инициализация будет xor eax,eax.

В цикле 5 -> 0 эта же константа 5 (4 байта) переместится в инициализацию перед циклом, но экономия на сравнении с нулем. Как показано в комментарии ниже, этот случай действительно может быть оптимизирован за счет конструкции dec eax, jz label, , но там выигрыш будет 1-2 байта в размере.

Это скорее для старых процессоров или спортивного программирования.

UFO just landed and posted this here
Тогда скорее
dec eax
jz label


Но компилятор может найти и более интересные варианты.

loop/loopz/loopnz забыли... И не зря: она медленнее чем несколько инструкций проверки/перехода.

Согласен. Тем более, что в Си для этого есть специальный оператор "goes toward":

int i = N;
while (i --> 0) { ... }

Цикл будет выполнен для i от N-1 до 0, всего N раз

Никакой это не специальный оператор.
После автоматического форматирования получится 2 оператора (--, пробел, >).
while (i-- > 0) { ... }

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

В ситуациях, когда счетчик цикла участвует в теле цикла, такая оптимизация навскидку не применима.

Не надо пытаться в голове переводить С код в машинный код. В 9 случаях из 10 ваши мысленные переводы будут мало коррелировать с реальностью. С - не ассемблер.

А сколько компиляторов Си вы знаете и сколько из них себя уважают?
И, возможно, ваши мысленные переводы и ошибаются в 9 из 10 случаев. Мои - в 1 из 10.

А разве компиляторы уже давно не делают это сами при подходящих обстоятельствах?
В целом список полезный, хотя, как уже многие написали выше, некоторые правила появились от несовершенства старых компиляторов и без них можно обойтись, улучшив читаемость кода. Очень рекомендую включить как можно больше всяких warnings и werror, чтобы отловить максимум ошибок и тонких мест на этапе компиляции. Вот хорошая статья по теме: interrupt.memfault.com/blog/best-and-worst-gcc-clang-compiler-flags

Согласен с большинством пунктов: 1,2,4, 5,8,9,11,12,14,16,23,24,26,27,33,35,37,41,44.

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

Те с которыми не могу согласиться в том виде в каком они сформулированы:

(6) - Enum для перечислений. Если константа - "номер протокола USB", то для нее enum логичен, если "максимальное число элементов в списке" - нет.

(7) и (20) по-моему противоречат друг другу. Если у нас один ретурн то нет никакого вреда от функций в условии, ведь мы можем зайти в нее и поставить точку останова на этот return. при этом оба будут раздувать код и из тривиальной функции на три строчки могут сделать монстра на полэкрана.

(15) опять же, при буквальном соблюдении вместо

void exec_task_dac(void * argument)
{
  init_dac();
  while(1)
  {
  	process_dac();
    osDelay(1);
  }
}

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

(19), (20), (29) - в IDE есть Find Usages, которая правильно находит все вхождения даже если переменные называются одинаково. Поэтому это в определенных случаях удобно, но в атрибут хорошего кода я бы возводить не стал.

(21) - сразу нет. if(get_count() > MAX_COUNT - RESERVE) сразу понятно что элементов слишком много, if(MAX_COUNT - RESERVE < get_count()) требует в уме переворачивать условие. Как кодестайл в конкретном проекте может и ок, но читаемости скорее мешает.

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

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

(38) - к счастью, gcc ругается на if( a = something), так что не вижу смысла уродовать код.

(39) - часто использую двухстрочные условия типа

if(no_action_required)
  return;

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

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

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


Если проводить аналогию из медицины, то это как центрифугирование крови. Си код тоже надо разделять на фракции!

15… Проще говоря, не должно быть функций, которые возвращают void
32. Функции CamelCase переменные snake_case

void proc_some_data(unsigned char* inBuffer, ....)
UFO just landed and posted this here
20–У каждой функции должен быть только 1 return. Это позволит дописать какой-то функционал в конце, зная, что он точно вызовется.

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

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

Из приведенных правил очень немногие специфичны для Си, большинство применимо (практически) к любому языку

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

В целом всё так, но про "все константы" это наверное зря. Я не раз встречал код, авторы которого слишком буквально понимали правило "не допускать чисел в коде". Константами там было задано вообще всё, включая 1 и 0. Это был код а-ля

x = (y >> TEN) & ONE;

Это хуже читается, искать баги становится сложнее, ведь каждую такую константу нужно проверять, а на самом ли деле ONE равно 1, а вдруг автор совсем не то имел в виду.

Ещё один пример - когда константами забиваются значения битов/полей в регистре. Да, это отличное правило, и в общем случае ему очень желательно следовать. Но, опять-таки, на практике встречается код с огромным количеством таких констант в h-файле, скажем, драйвера, а в его си-файле по факту в паре регистров пара битов проверяется, и всё. Когда осознаёшь, что кода могло бы быть в разы меньше, а сам он был бы сильно проще и понятнее, категоричность суждений снижается, и понимаешь, что правила правилами, но в отдельных случаях правильнее, как ни странно, может оказаться от них отступить. И проверить нужный бит явно, в комментарии указав, что именно мы тут делаем и в каком пункте даташита об этом говорится. Работа поддерживающего программиста станет проще в разы.

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

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

Когда пишешь что-то (потенциально) мультиплатформенное, или, не дай бог, протокол обмена, то битовые поля - это боль. Сталкивался со случаем, когда в протоколе обмена битовым полем была описана большая структура. И всё ожидаемо поломалось, когда в какой-то момент обмениваться пришлось с новой системой, с другим порядком байтов. В тот раз несколько дней потратили на ручной "переворот" битовых полей. И теперь в том протоколе две структуры - одна для BE, другая для LE, что, конечно же, не очень здорово из-за дублирования кода.

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

state.result = SOME_ERROR_CODE;

При этом, SOME_ERROR_CODE равно двум, поле bar имеет тип int, но в результате записи там почему-то оказывается ноль. Почему? А потому что автор объявил поле так:

int result: 1;

В тот момент ему хватало двух значений для кода результата, но в какой-то момент ему понадобились ещё коды, он их добавил, а битов для поля не добавил. С точки зрения языка, тип поля - int, IDE показывает, что тип поля - int, число 3 в int влезает, компилятор не ругается. Так этот баг в итоге и остался на какое-то время незамеченным.

14–Если переменная это физическая величина, то в суффиксе указывать размерность (timeout_ms). Это увеличивает понятность кода.

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

15–Все Си-функции должны всегда возвращать код ошибки. Минимум тип bool или числовой код ошибки. Так можно понять, где именно что-то пошло не так. Проще говоря, не должно быть функций, которые возвращают void. 

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

Вот вы, например. часто проверяете, что там printf вернул? Многие и не знают даже, что он что-то возвращает. Таких примеров множество.

Представим, что мы написали библиотеку для растровой графики. Там есть функция gfxInit(). Должна ли она возвращать код результата? Скорее всего да, должна, ведь инициализация может пройти неудачно, скажем, из-за нехватки памяти, из-за аппаратных причин (железо нужное на шине не нашли), и т.п. В этом случае, очевидно, ПО должно как-то отреагировать на критический сбой. А должны ли возвращать ошибку функции навроде gfxPutPixel() или gfxDrawLine()? Моё мнение - нет. Не нужно заставлять программиста, использующего ваш интерфейс, засорять свой чистый и понятный код отрисовки графики бесполезными проверками. Код станет нечитаемым адищем, а пользы для дела - ноль. Гораздо полезнее в подобных случаях (когда подсистема/библиотека может "отвалиться" в процессе работы) создать отдельную функцию для получения текущего состояния - и вот её уже можно будет вызывать периодически (один раз за такт в главном цикле работы, например), чтобы узнать, всё ли там в порядке.

О, есть прикол с битовыми полями.... Злобный.

struct { int field : 1; } v;
v.field = 1;
assert(v.field == 1); // FAIL

Кто догадался в чём дело?

"Догадался"?

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

Во-вторых, в языке С (в отличие от С++) тип int не является синонимом signed int в таком контексте. Битовое поле может оказаться знаковым, а может и беззнаковым. Это определяется реализацией. То есть в зависимости от implementation-defined факторов данный assert может выстрелить, а может и не выстрелить.

Вот бы и для C++ такой набор советов.

Список советов:


  1. Не пишите на C++.

Вы хотели сказать "0. Не пишите на С++, пишите на Rust"?

Не хочу, мне комитет Rust evangelism strike force задерживает выплаты.

Согласен. С++ не подходит для программирования MК так как С++ запрещает стандартом брать адрес функции main. А это необходимо для run-time проверки, что первичный, вторичный загрузчики и многоядерные сборки Generic(ов) легли в нужные адреса NorFlash(а).

С++ прекрасно подходит для программирования МК.

Программирование MK, как впрочем и любое программирование, нацеленное на конкретный экземпляр или класс хардвера, всегда и везде будет делаться с существенным привлечением нестандартных/надстандартных гарантий и возможностей, предоставляемых конкретным компилятором. Цитировать запреты стандарта при это смысла нет. Если конкретная реализация С++ позволяет вам брать адрес main и вам это действительно нужно - берите его. Это в одинаковой мере относится и к С, и к С++, и к любому другому ЯВУ.

В частности, с педантичной точки зрения фразы типа "Линукс написан С" являются бессмысленными, ибо на стандартном С невозможно реализовать ОС. Все прекрасно понимают, что в такой реализации будут использоваться нестандартные/расширенные возможности конкретного компилятора. Это же в равной мере относится и к программированию МК.

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

UFO just landed and posted this here

"19–В идеале все переменные должны иметь разные имена. Так было бы очень
удобно делать поиск по grep. Но тут надо искать компромисс с
наглядностью."
И правда! Кому нужны эти буржуазные IDE ?

Американский профессор Jacob Sorber, доказывает, что люди, которые привыкли программировать в IDE имеют тенденцию становиться очень слабыми программистами.

https://www.youtube.com/watch?v=Pqf6H1WSbeY

потому, что User(ы) IDE не понимают какой путь проходят исходники с момента написания до попадания в Flash. За них всё делает IDE.

UFO just landed and posted this here

Есть такое. Постоянно сталкиваюсь с тем, что новички, да и не только новички, к сожалению, не понимают разницы компиляции и линковки. Часто бывает, что видя при сборке проекта ошибку "unresolved reference", не могут понять, что это не ошибка компиляции, а просто библиотеку не подключили нужную. Или в имени функции при вызове ошиблись. В итоге, тратят на решение подобных, казалось бы, простых проблем, слишком много времени, зачастую совершая при этом какие-то рандомные действия. От IDE отказываться никто не призывает, наоборот, фичами IDE нужно пользоваться во всю, если они облегчают рутинную работу, но всё-таки нужно же знать, как работает твой инструмент. И было бы здорово, если бы обучение языку начиналось именно с этого - сначала пишем файл исходника и вручную вызываем для его сборки компилятор, затем добавляем ещё файлов и учимся делать простой мейк-файл, а уже затем переходим в IDE. Тогда и действия IDE уже не будут восприниматься как сложная и непонятная магия. Наверное.

Если вы собираете сорцы из Makefile, то вы можете инициировать сборки прямо из командной строки. А это значит, что процесс сборки можно автоматизировать. Прикреплять в Jenkins. А утром контролировать результаты сотен сборок, что отработали ночью. Производительность работы с Makefile выше.

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

А что мешает иметь процесс сборки, который запускается и в IDE, и вне её?

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

Это даже не говоря о том, что доминирование Windows и MS Visual Studio, как инструмента повседневной разработки для всех целевых платформ привело к тому, что средства автоматической конвертации проектов Visual Studio в makefile или любой другой целевой формат доступны в огромном количестве. Стандартный flow для прикладного ПО под Linux: повседневная разработка в MSVS под Windows -> автоматическая конвертация vcxproj в makefile -> сборка и тестирование под Linux.

В MakeFile нет xml для каждого проекта. Как в IDE.
Это значит, что добавление и исключение сотен .с файлов для сотен сборок можно делать одной строчкой в make.

Ну тут можно еще добавить такие операции, как перетасовку содержимого файлов в случайном порядке, архивирование сотен файлов и посылку их на FTP, автоматическую массовую замену идентификаторов на имена персонажей из LotR, проигрывание MP3 музыки из файлов проекта с соответствующим расширением и еще бесчисленное множество разнообразных действий, которые никому на самом деле не нужны за пределами статистической погрешности.

И это при том, что "добавление и исключение сотен .с файлов" не представляет никакой сложности и в IDE. 

И это при том, что "добавление и исключение сотен .с файлов" не представляет никакой сложности и в IDE. 

Лож! В IDE IAR это большая сложностью.

Может быть это повод сменить IDE?

Это хорошее предложение.
Но тут такое дело. Компания купила за 40млн руб у OutSourcer(ов) код прошивки и те по глупости нафигачили всё в IAR. В OutSourcer работали ВУЗ(овцы) и они просто ничего не знали кроме IAR. Так как у них в универе была какая-то лаба в IAR.

Теперь от IAR так просто не избавится. Особенно сейчас в перид санкций это совсем не круто зависеть от ЕС. Плюс пришлось уже заплатить 10500 евро Шведам за 3 лицензии этой пресловутой IDE, чтобы хоть как-то работать с горе-Legacy от OutSourcer(ов) (нет модульных тестов, нет UART-CLI, спагетти-код).

Понадобится инфраструктурная неделя длительностью минимум 4 месяцев, чтобы перевести все 53 конфигурации IAR на сборки из Make. Но на эту уже нет ресурсов.

Да и руководство считает, что товар хороший, раз расходы такие большие.

Недавно мне как раз пришлось искать и удалять все неиспользованные исходники из проекта (притом с чётким осознанием того, что линкер всё равно всё неиспользуемое в бинарник не положит, просто ради красоты отчёта статического анализатора на стороне заказчика) и могу сказать, что чтобы удалить программно исходник из проекта IAR, нужно удалить его запись сразу из 5 проектных файлов - иначе весьма высока вероятность того, что он волшебным образом вернётся обратно при последующем открытии IDE... И причины использования ровно те же - заказчик заплатил за него, а значит все будут страдать. Особенно увлекательно выглядит процесс работы в команде, когда заказчик расщедрился аж на целую ОДНУ сетевую лицензию на команду - то есть если в команде кто-то запустил в IAR сборку - все остальные не смогут пользоваться компилятором на своих машинах в ближайшие полчаса...

У IDE IAR еще отсутствует обратная совместимость.
IAR 7.5 не откроет проект IAR от 8.3.

Если какой-н OutSourс(er) пере сохранит код в более новом IAR ,то он просто угробит проект для остальных вкладчиков.

Поэтому make рулит.

ёжики плакали, кололись...

При сборке из Make можно одной строчкой включать/исключать из сотен сборок сотни файлов.

В случае с IDE пришлось бы неделю редактировать *.xml(ки) *.ewp, чтобы сделать что-то подобное. Причем если ошибешься с пробелом или запятой в этих IDE(шных) xml, то проект вообще покорраптится и даже не откроется.

IDE это для школоты и протопитирования, когда 1 конфигурация и 1 сборка.


Вы сейчас про какую именно IDE говорите?

O_o. Все повседневная разработка прикладного ПО ведется именно в IDE, ибо именно такой мгновенной интегрированной доступ к средствам сборки и отладки абсолютно необходим для эффективной разработки. IDE и родились из требований разработчиков. Повседневная разработка без IDE сегодня - это упрямое пожирание кактуса.

Именно из-за того, что MS Visual Studio с гигантским отрывом является самой мощной, удобной и эффективной IDE на сегодняшний день, и сложилась нынешняя парадоксальная ситуация, когда разработка прикладного ПО под все целевые платформы в 99 случаях из 100 делается под Windows в Visual Studio. Как это ни удивительно, но никто за рамками статистической погрешности не разрабатывает прикладные продукты для Линукса под Линуксом. Все разрабатывается под Windows и лишь интегрируется и тестируется на целевой платформе.

UFO just landed and posted this here

IAR использует xml как исходник

Keil использует xml как исходник

CodeCopposerStudio использует xml как исходник
AtolicTrueStudio в режиме авто генерации make файлов использует xml как исходник

UFO just landed and posted this here

Раньше приходилось работать с больным legacy кодом в IAR.

Новые же проекты я собираю из Make.

*35--Для синтаксического разбора регистров использовать объединения вкупе с битовыми полями.

Язык С сам по себе не поддерживает такого использования битовых полей. В спецификации битовых полей специально подчеркивается, что способ их размещения в памяти никак не оговаривается вообще. Подобное использование - implementation-dependent хак.

Битовые поля были введены в язык для экономии памяти, а не для разбора фиксированных битовых карт.

21–Не использовать операторы >, >= Вместо них использовать <, <= просто поменяв местами аргументы там, где это нужно. Это позволит интуитивно проще анализировать логику по коду. Человеку еще со времен школьной математики понятнее, когда то, что слева - то меньше, а то, что справа - то больше. 

Очень спорный совет, опирающийся на формальную, а не не человеческую логику. В случаях, когда оба сравниваемых значения имеют одинаковый "приоритет" - да, это хорошая идея. Но в подавляющем большинстве случае роли сравниваемых значений ассиметричны: есть тот, "кого" мы сравниваем, и есть тот, "с кем" мы сравниваем. Это особенно выраженно когда первое значение постоянно меняется (напр., в процессе поиска), а последнее - более-менее фиксировано. Именно в таком порядке и стоит записывать сравнения: сначала "кого", потом "с кем". Такая запись намного лучше передает естественный ход человеческой мысли. Если в таком порядке записи придется использовать оператор >, то так тому и быть.

*30--Если в коде есть список чего-либо (прототипы функций, макросы, перечисления), то эти строки должны > быть отсортированы по алфавиту.

Лучше группировать по семантике, а не по алфавиту.

46-- Include(ы) всегда должны только содержать название конечного файла. Include(ы) не должны содержать > часть пути к файлу.

Для файлов в подкаталогах, которые не используются по всему проекту, нет смысла добавлять путь в опции компилятора. А включать их с относительным путем, например #include "cyclical_buff/cyclical_buff.h"

Как в С определить знаковый 24х битный тип данных для переменной int24_t?

знаковая целочисленная переменная у которой sizeof(int24_t) равен 3м байтам

В C++ легко, в Сях сделать так, чтобы ещё и операторы работали как ожидается - извините, любители Си отдыхают.

Sign up to leave a comment.

Articles