Пролог
Настал тот первый день, когда в программировании микроконтроллеров наконец пригодилась такая абстрактная структура данных как LIFO. Он же стек. Он же магазинная память.
На бытовом уровне все мы так или иначе имели дело со стеком. Стек это, по сути, стопка игральных карт на столе, патроны в магазине штурмовой винтовки AK-47, стопка тарелок на кухне, свежеиспечённые блинчики на тарелке, стопка книг на столе, RAM память для локальных переменных внутри компьютерных программ тоже растет и уменьшается по правилу LIFO, говорят про стек протоколов в модели ISO-7. Синтаксический разбор XML файла требует работы LIFO. Даже детская дошкольная игрушка Ханойская башня - это тоже LIFO.
Всё это примеры стека (LIFO). Это когда брать и класть "чиво-либо" можно только с одной стороны.
Сейчас объясню при каких именно обстоятельствах мне понадобился стек на работе ...
У нас в организации существует обязательное внутреннее требование к оформлению исходных текстов программ на языке программирования Си для микроконтроллеров, которое звучит так:
блок кода xxx() {} должен заканчиваться комментарием "end of ...." (см. шаблон)
В переводе на кухонный язык это значит, что в конце каждого блока if(...) {...} ; switch(...) {...} ; for(...) {...} и т.п. необходимо пиcать комментарий
// end of if(...). end of switch(...) end of for(...) соответственно.
Правило обязательно к применению, если блок с фигурными скобками охватывает больше чем 4 строчки кода, а для функций правило обязательно во всех случаях.
Вот так выглядит это художество в образцовом куске кода: Это тот самый "см. шаблон", который надо как попугай всегда и везде повторять.
//**************************************************************************************************
//! [Description of MODULE_FunctionTwo]
//!
//! \note [text]
//!
//! \param[in] parameterZero - [description of parameterZero]
//! \param[in] parameterOne - [description of parameterOne]
//!
//! \return [Description of return value]
//**************************************************************************************************
static DATA_TYPE MODULE_FunctionTwo(DATA_TYPE parameterZero,
DATA_TYPE parameterOne)
{
DATA_TYPE returnValue;
// [Description...]
switch (expression)
{
case CASE_ONE:
caseOneCnt++;
break;
case CASE_TWO:
caseTwoCnt++;
break;
default:
caseDefaultCnt++;
break;
} // end of switch (expression)
return returnValue;
} // end of MODULE_FunctionTwo()
Как можно заметить, тут после switch присутствует комментарий // end of switch (expression). И в конце имени функции тоже // end of MODULE_FunctionTwo().
У нас на цензуре в Gerrit коммит просто не примут в ветку main, если хотя бы в одном месте нет этого комментария // end of xxx(). Для этого у нас в организации есть специальный надзиратель - апологет именно правила end of xxx(), который даже не глядя на функционал программного компонента и модульные тесты (про которые он, к слову, даже ничего не слышал) просто банит коммиты, если хотя бы в одном месте нет этого текстового комментария // end of xxx(...) .
А теперь внимание...
Сам программист-апологет требования комментариев // end of xxx() это требование в своих исходниках игнорирует!
По прошлому тексту многие не верили, что такое вообще возможно. Вот я в качестве доказательства выкладываю скриншот.
Вот так... Правила, да не для всех оказывается...
Даже язык не поворачивается назвать такие порядки словом "инспекция программ". Это самая настоящая цензура.
Вот такие пирожки с капустой... Понимаете? А мы с этим живем...
Понятное дело, что эти комментарии компилятору, да и для модульных тестов функционала нужны, как собаке бензобак.
Однако, требование такое есть. Раз надо, так надо...
В чём проблема?
Проблема в том, что вручную прослеживать везде исполнение этого нелепого правила очевидно очень утомительно. Особенно в файлах, где уже 5000-7000+ строк кода.
Поэтому, как ни крути, тут нужна волшебная палочка - консольная утилита-локатор, которая сама будет находить все места, где отсутствуют комментарии // end of xxx() после закрывающейся фигурной скобочки }.
При этом за 30 лет существования этого правила в этой организации никто из программистов эту утилиту тут так и не написал. За 30 лет! Да, господа... Вот так...
Этой утилиты не существовало в природе до сегодняшнего дня. Запомним этот день!
Любая разработка начинается только тогда, когда появляются полноценные средства для отладки. Подобно тому как альпинизм начинается с верёвок.
Реализация
Текстовое описание алгоритма
Решить эту задачу можно при помощи такой классической структуры данных как LIFO (стек). Идея в следующем.
Открыть *.c файл и читать его строчка за строчкой и символ за символом. Как только встретится символ { положить в стек структуру, которая запомнит номер строки и тип скобки. Когда встретиться } тоже запомнить в стек структуру с информацией про скобку.
№ |
Переменная |
Возможные значение |
1 |
тип скобки |
{ или } |
2 |
номер строки |
натуральное число |
После каждого добавления в стек скобки } следует проверять можно ли сократить скобки. Если предпоследний элемент в стеке это {, а последний элемент в стеке это }, то можно сократить.
В этом случае мы извлекаем два последние элемента из LIFO. Вычисляем разницу номеров строк. Если значение больше 4 - проверяем есть ли комментарий // end of xxx(). Если есть, то идем дальше. Если нет, то выдаём красное сообщение ошибки, что на строке L отсутствует комментарий // end of xxx() и увеличиваем счетчик ошибок.
Если видит }, или }; то пропускать такие строчки. Это не конец функции или оператора, а конец структуры или массива.
Вот так просто и не затейливо. Между делом, утилита ещё и проверяет баланс открытых и закрытых фигурных скобочек. И так до конца *.c файла.
При этом утилиту я написал на Си буквально за два вечера и ядро функционала составило менее чем 250 строк.
ядро утилиты
#include "end_of_block.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "log.h"
#include "file_pc.h"
#include "lifo_array.h"
#include "str_utils_ex.h"
#include "csv.h"
bool end_of_block_mcal_init(void) {
bool res = true;
log_level_get_set(LINE, LOG_LEVEL_INFO);
log_level_get_set(END_OF_BLOCK, LOG_LEVEL_INFO);
LOG_INFO(END_OF_BLOCK, "END_OF_BLOCK_VERSION:%u", END_OF_BLOCK_DRIVER_VERSION);
return res;
}
static bool end_of_block_proc_brace_open(EndOfBlockHandle_t *const Node) {
bool res = false;
//push to stack
BraceInfo_t* Brace = (BraceInfo_t*) malloc(sizeof(BraceInfo_t));
if(Brace) {
Brace->dir = BRACE_DIR_OPEN;
Brace->line_number = Node->cur_line;
Brace->code = END_OF_BLOCK_ID;
Array_t Elem = {0};
Elem.pArr = (uint8_t*)Brace;
Elem.size = sizeof(BraceInfo_t);
res = lifo_arr_push(&(Node->LifoArray), Elem);
if(res){
LOG_DEBUG(END_OF_BLOCK, "Line: %7u ,LifoArrayPush {",Node->cur_line);
}else{
LOG_ERROR(END_OF_BLOCK,"ErrPush");
}
}else{
LOG_ERROR(END_OF_BLOCK,"ErrMalloc");
}
return res;
}
static bool EndOfBlockIsValidBrace(BraceInfo_t* Node){
bool res = false;
if(Node) {
if((BRACE_DIR_CLOSE==Node->dir) || (BRACE_DIR_OPEN==Node->dir))
{
if(0<Node->line_number){
if(END_OF_BLOCK_ID==Node->code){
res = true;
}else{
LOG_ERROR(END_OF_BLOCK,"NoCodeID");
}
}else{
LOG_ERROR(END_OF_BLOCK,"NotLine");
}
}else{
LOG_ERROR(END_OF_BLOCK,"NotBrase");
}
}
if(false==res){
LOG_ERROR(END_OF_BLOCK,"%s",BraceInfoToStr(Node));
}
return res;
}
bool end_of_block_try_reduce(EndOfBlockHandle_t *const Node) {
bool res = false;
LOG_DEBUG(END_OF_BLOCK, "TryReduce");
Array_t PrevNode={0};
res = lifo_arr_peek_num(&(Node->LifoArray), 0, &PrevNode);
if(res) {
res = LivoIsValidItem(&PrevNode);
BraceInfo_t PrevBrace={0};
PrevBrace = *((BraceInfo_t*) PrevNode.pArr);
//memcpy((void *)&PrevBrace,(void *)PrevNode.pArr,sizeof(BraceInfo_t));
//memcpy((void *)&PrevBrace,(void *)PrevNode.pArr,sizeof(BraceInfo_t));
Array_t PrevPrevNode={0};
res = lifo_arr_peek_num(&(Node->LifoArray), 1, &PrevPrevNode);
if (res) {
res = LivoIsValidItem(&PrevPrevNode);
BraceInfo_t PrevPrevBrace;
PrevPrevBrace = *((BraceInfo_t*) PrevPrevNode.pArr);
//memcpy((void *)&PrevPrevBrace,(void *)PrevPrevNode.pArr,sizeof(BraceInfo_t));
res = EndOfBlockIsValidBrace(&PrevBrace);
res = EndOfBlockIsValidBrace(&PrevPrevBrace);
if(BRACE_DIR_CLOSE==PrevBrace.dir) {
if(BRACE_DIR_OPEN==PrevPrevBrace.dir) {
LOG_DEBUG(END_OF_BLOCK, "Spot{} pair");
Node->pair_cnt++;
uint32_t line_diff = PrevBrace.line_number - PrevPrevBrace.line_number;
if (Node->line_threshold < line_diff) {
char* subStr = strstr(Node->curLine,"end of");
if(subStr) {
res = true;
Node->ok_counter++;
} else {
Node->violation_counter++;
LOG_ERROR(END_OF_BLOCK,"Err:%3u,%s:Line: %7u ,lack[ // end of xxx() ]",
Node->violation_counter,
Node->fileShortName,
PrevBrace.line_number
);
res = true;
}
}
res = lifo_arr_delete_cnt(&(Node->LifoArray), 2) ;
}
}
}else{
LOG_ERROR(END_OF_BLOCK,"PeekErr");
}
}else{
LOG_ERROR(END_OF_BLOCK,"PeekErr");
}
return res;
}
static bool end_of_block_proc_brace_close(EndOfBlockHandle_t *const Node) {
bool res = false;
//push to stack
BraceInfo_t *Brace = (BraceInfo_t*) malloc(sizeof(BraceInfo_t));
if(Brace) {
Brace->dir = BRACE_DIR_CLOSE;
Brace->line_number = Node->cur_line;
Brace->code = END_OF_BLOCK_ID;
Array_t Elem;
Elem.pArr = (uint8_t*)Brace;
Elem.size = sizeof(BraceInfo_t);
res = lifo_arr_push(&(Node->LifoArray), Elem);
if(res){
LOG_DEBUG(END_OF_BLOCK, "Line:%3u,LifoArrayPush }",Node->cur_line);
res = end_of_block_try_reduce(Node);
}else{
LOG_ERROR(END_OF_BLOCK,"ErrPush");
}
}else{
LOG_ERROR(END_OF_BLOCK,"ErrMalloc");
}
return res;
}
static bool end_of_block_proc_byte(EndOfBlockHandle_t* Node, char letter) {
bool res = false ;
switch(letter){
case '{': {
res = end_of_block_proc_brace_open(Node);
} break;
case '}': {
res = end_of_block_proc_brace_close(Node);
//try reduce
res = true;
} break;
case '\n': {res = true;} break;
case '\r': {res = true;} break;
default: {res = true;} break;
}
return res;
}
static bool end_of_block_proc_line(EndOfBlockHandle_t* Node){
bool res = true;
uint32_t len=strlen(Node->curLine);
uint32_t i = 0 ;
uint32_t ok_cnt = 0 ;
for(i=0;i<len;i++){
res = end_of_block_proc_byte(Node, Node->curLine[i]);
if (res) {
ok_cnt++;
} else {
LOG_ERROR(END_OF_BLOCK, "ProcByteErr:[%c]",Node->curLine[i]);
}
}
if(len==ok_cnt) {
res = true;
}else{
LOG_ERROR(END_OF_BLOCK, "ProcLineErr:[%s]",Node->curLine);
res = false;
}
return res;
}
bool end_of_block_check(const char *const file_name_c, uint32_t lines ) {
bool res = false;
if (file_name_c) {
if( 0 < lines) {
EndOfBlockHandle_t EndOfBlock={0};
EndOfBlock.line_threshold = lines;
res = file_pc_realpath(file_name_c, EndOfBlock.fileNameC);
if(res) {
Array_t LifoArray[800] = {0};
res = csv_parse_last_text(EndOfBlock.fileNameC, '/',
EndOfBlock.fileShortName,
sizeof(EndOfBlock.fileShortName) );
if(res) {
res = lifo_arr_init(&EndOfBlock.LifoArray, LifoArray, ARRAY_SIZE(LifoArray));
log_res(END_OF_BLOCK, res, "LiFoInit");
if(res) {
LOG_DEBUG(END_OF_BLOCK, "CheckEndOfBlockCommentIn:[%s]", EndOfBlock.fileNameC);
EndOfBlock.filePtr = fopen(EndOfBlock.fileNameC, "r");
if(EndOfBlock.filePtr) {
LOG_DEBUG(END_OF_BLOCK, "OpenOkFile:[%s]", EndOfBlock.fileNameC);
while(NULL != fgets(EndOfBlock.curLine, END_OF_BLOCK_MAX_LINE_SIZE, EndOfBlock.filePtr)) {
EndOfBlock.cur_line++;
LOG_PARN(END_OF_BLOCK, "%u,%s",EndOfBlock.cur_line, EndOfBlock.curLine);
res = end_of_block_proc_line(&EndOfBlock);
if(res) {
EndOfBlock.ok_cnt++;
}else{
EndOfBlock.err_cnt++;
}
}
fclose(EndOfBlock.filePtr);
}
}
}
}else{
LOG_ERROR(END_OF_BLOCK, "OpenFileErr:[%s]", EndOfBlock.fileNameC);
}
if(0==EndOfBlock.err_cnt) {
res = true;
} else {
LOG_ERROR(END_OF_BLOCK, "Err:%u", EndOfBlock.err_cnt);
}
LOG_INFO(END_OF_BLOCK, "%s", EndOfBlockNodeReportToStr(&EndOfBlock));
}
}
return res;
}
Как можно заметить, эта утилита использует такие программные зависимости как LIFO, CSV, LOG, STRING и FILE. Подразумевается, что у Вас в репозитории уже присутствует реализация на Си этих программных компонентов SWC.
Однако в организации 600+ программистов за 30 лет никто даже этого не сделал. Это как?
Отладка
Мне удалось написать на Си консольную утилиту, которая находит в Си-коде все места, где отсутствуют комментарий после закрывающейся фигурной скобки }.
Взводится утилита следующим образом. Надо просто осуществить пуск *.bat файла с таким содержимым.
:: Windows cmd script
cls
set line_threshold=5
set file_name=C:/project/HAL/GPIO/gpio_drv.c
code_style_check.exe eob %line_threshold% %file_name%
:: eob - end of block
Вот такой лог метаданных выдает утилита:
Вот и в другом файле с исходниками утилита обнаружила многочисленные нарушения нашего внутреннего code-style. Непорядок...
Благодаря этому логу можно смело откопать все места и ликвидировать ошибки, добавив там комментарии // end of xxx().
Дистрибутив утилиты
Если вы являетесь коллегой по несчастью, то можете тоже взять у меня готовую утилиту code_style_check.exe для решения задачи поиска отсутствующих комментариев.
Скачать tool(у) можно у меня с github
Итог
Удалось автоматизировать процесс проверки правила с порядковым номером №456 нашего внутреннего code-style. Да, господа, у нас более четырех сотен обязательных правил в компанейском code-style...
Добиться этого удалось при помощи отдельной специально разработанной консольной радар-утилиты целеуказания. Утилита автоматически локально выявит недочеты в коде ещё до комита в общак.
Надеюсь, этот текст и выложенная в открытый доступ утилита помогут Вам в прохождении цензуры при разработке программ для микроконтроллеров в России.
Словарь
Акроним |
Расшифровка |
SWC |
SoftWare Component |
LIFO |
last-in-first-out |
ISO |
International Organization for Standardization |
CSV |
Comma-separated values |
Ссылки
У меня присутствуют утилиты для выявления и других правил нашего внутреннего code-style. Про них можно почитать тут
Название текста |
URL |
|
1 |
Стилистический анализатор: синхронизация объявлений и определений static функций |
|
2 |
Стилистический Анализатор: Синхронизация порядка объявлений и определений функций |
|
3 |
Интеграция Стилистического Анализа в общий Make Скрипт Сборки Проекта |
|
4 |
Нельзя Просто Так Пойти и Купить Овцу (или Потёмкинская Деревня в Коде) |
|
5 |
Интеграция clang-format в Процесс Сборки |
|
6 |
Почему Сборка с Помощью Есlipse ARM GCC Плагинов это Тупиковый Путь |
|
7 |
Дистрибутив утилиты code_style_check.exe |
|
8 |
Синтаксический разбор CSV строчек |
Вопросы
1-- Зачем в конце if(...) {} писать if(...) {} // end of if(...)?
Комментарии (92)
FilimoniC
12.12.2024 16:07В программировании микроконтроллеров, кажется лишним закрывать блоки комментариями, т.к. внутри этого блока можно легко встретить оптимизационную дичь вроде goto-инструкций.
YDR
12.12.2024 16:07я бы не стал использовать goto даже в этом случае. Не сильно то оно и оптимизирует по сравнению с нормальным стилем.
vadimr
12.12.2024 16:07Всё зависит от характера кода. В продвинутой работе с железом без go to сложно обойтись, так как логика выполнения определяется фактическими событиями в аппаратуре, а не паттернами проектирования. А высокоуровневые абстракции для такой модели очень дороги.
Jijiki
12.12.2024 16:07тема классная, я форматер писал для интереса (парсер + форматер - как раз по этим скобочкам ориентировался)
randomsimplenumber
12.12.2024 16:07Если бы мне вдруг понадобилось решать подобную задачу - моя программа сама бы расставляла правильные комментарии.
aabzel Автор
12.12.2024 16:07Если бы мне вдруг понадобилось решать подобную задачу - моя программа сама бы расставляла правильные комментарии.
То, что мы сейчас прочитали - это именно то, что нужно для решения всей проблемы.
Предлагаю Вам @randomsimplenumber до конца месяца написать, эту утилиту и написать тут на habr пояснительную записку про неё.
Чтобы уже в ближайшее время можно было автоматически проставлять корректные комментарии // end of xxx(...) после закрывающихся фигурных скобок во всем *.c файле.
aamonster
12.12.2024 16:07Сколько платите за решение?
aamonster
12.12.2024 16:07Угу. Для линтеров такая возможность – база. А ещё удобнее плагин к используемой IDE, который подсказывает эти подстановки.
serchu
12.12.2024 16:07А можно узнать какую проблему решает это правило? Не имел опыта с программированием микроконтроллеров, возможно, поэтому правило выглядит надуманным
aabzel Автор
12.12.2024 16:07какую проблему решает это правило? .... правило выглядит надуманным
Вот именно, дружище, что это правило никакой реальной проблемы не решает.
Вы правы. Правило комментария после } - это самое настоящее надуманное правило.
У нас в организации таких правило больше 400+ и у нас регулярно в чатах люди жалуются на то, что внутренний код-стайл давно доведён до абсурда.
brumbrum
12.12.2024 16:07Такую проблему стоит решать иначе — уходить из этой организации.
aabzel Автор
12.12.2024 16:07На самом деле это даже здорово придумывать всё новые и новые требования к code style. Так можно аргументировать раздувание фронта работ начальству, что тебе и коллегам гарантирована оплачиваемая работа на обозримую перспективу. Можно годами как пиявка высасывать из компании зарплату за то, что с меньшими требованиями к code style можно сделать максимум за 2-3 месяца
randomsimplenumber
12.12.2024 16:07На самом деле это даже здорово
Ну, если всё всех устраивает - в чем проблема то? Автоматических средств проверки ваших 400 правил хорошего тона нет, цензурится вручную, но не очень строго. Написание 400 костылей займёт 400 * 2 дня = почти 3 года. И то, за это время могут ещё правил придумать. Если уж пилить - то что-то новое для себя, а не повторение темы, которую проходили когда то на 2 курсе. AI модная тема, flex - хз что это но выглядит интересно, ну и прочее ненормальное программирование ;)
aabzel Автор
12.12.2024 16:07Ну, если всё всех устраивает - в чем проблема то?
Прость за державу обидно.
Вместо развития техники и технологий как в 196x, теперь мы в 202x занимаемся IT-муштрой.
aabzel Автор
12.12.2024 16:07Зачем уходить ,если за код стайл тут платят зарплату? И не маленькую...
При этом писать комменты это много проще, чем чинить чужие баги на новой работе.
randomsimplenumber
12.12.2024 16:07Зачем уходить ,если за код стайл тут платят зарплату?
За державу обидно (ц), но 20 баксов это 20 баксов (тоже ц) ;) Налицо дилемма
вагонеткитрусов и крестика.Ну, кстати, вот что происходит, когда на программиста начинают натягивать странные KPI.
aabzel Автор
12.12.2024 16:07правило выглядит надуманным
Вот, можете ознакомиться с полным нашим парадом абсурда
Потёмкинская Деревня в Коде
https://habr.com/ru/articles/837396/randomsimplenumber
12.12.2024 16:07А у вас вся существующая кодовая база удовлетворяет этим требованиям?
А если кто-то вносит правки в условие - текст в комментариях тоже исправляет?
aabzel Автор
12.12.2024 16:07И да и нет.
Одним можно делать существенные отступления от существующего код-стайл.
По фамилиям эти одни в компании - родственники руководству.
Остальным нет. Остальных гоняют по IT-муштре по-полной.
aeder
12.12.2024 16:07А использовать проверенную десятилетиями связку flex+bison было слишком страшно?
Это ведь идеальная задача для парсера и грамматики.
Учитывая, что судя по вашему описанию - ваша утилита будет глючить на
комментариях, содержащих фигурные скобки
строковых литералах, содержащих фигурные скобки
символьных литералах, содержащих фигурные скобки
В общем, советую переделать по-взрослому.
Кстати, если немного подумаете - то сможете не просто проверочную утилиту реализовать, а реализовать утилиту, которая автоматом будет вставлять эти комментарии.
aabzel Автор
12.12.2024 16:07То, что мы сейчас прочитали - это именно то, что нужно для решения всей проблемы.
Предлагаю Вам @aeder до конца месяца написать, эту утилиту и написать тут на habr пояснительную записку про неё.
Чтобы уже в ближайшее время можно было автоматически проставлять корректные комментарии // end of xxx(...) после закрывающихся фигурных скобок во всем *.c файле.
aabzel Автор
12.12.2024 16:07А использовать проверенную десятилетиями связку flex+bison было слишком страшно?
Критикуешь- предлагай
Предлагая - делай
Делая - отвечайvindy
12.12.2024 16:07Все три утверждения - манипулятивная стратегия защиты в дискуссии.
aabzel Автор
12.12.2024 16:07Вы хоть знаете кому принадлежат эти слова?
IvaYan
12.12.2024 16:07Ссылка на авторитет -- не менее манипулятивная стратегия. У вас нет аргументов в защиту вашего утверждения, так что вам не остается ничего, кроме как сослаться на кого-то, кто более уважаем, чем вы, в надежде, что это сделает вашу точку зрения весомее.
vindy
12.12.2024 16:07Я не знаю, кому принадлежат эти слова. Так как услышал я их от вас, то буду благодарен, если вы сможете аргументировать, почему нельзя критиковать что-либо, не предлагая решения? Например, я ничего не понимаю в теплоснабжении жилых кварталов в своем городе, значит ли это, что я не должен критиковать коммунальную службу, которая меня в морозы оставила с холодными батареями на двое суток? Я совершенно искренне не знаю, что именно им нужно улучшить у себя, чтобы такое не происходило, я не теплотехник и не управленец. Мне точно из-за этого нужно молчать? Также, переходя к пункту 2 и 3 вашего утверждения, нужно ли мне молчать, если я не готов устранить эту аварию самостоятельно и взять на себя ответственность за качество ремонта?
Timick
12.12.2024 16:07Судя по всему элементарная задача по строковому интерпретатору формул (скобочки определяют порядок расчета). В 90-м решали такую на конкурсе Юный программист с использованием ПЭВМ Правец-8А
CitizenOfDreams
12.12.2024 16:07В конце каждого блока if(...) {...} ; switch(...) {...} ; for(...) {...} и т.п. необходимо пиcать комментарий // end of if(...). end of switch(...) end of for(...) соответственно.
Тоже так делаю, потом удобнее смотреть, где конец чего. Но, конечно, в абсолют не возвожу.
С другой стороны, я пишу сам для себя программы для мелких микроконтроллеров, вплоть до PIC10 (512 байт ПЗУ, 64 байта ОЗУ, 200-300 строк кода). Возможно, при совместной работе над более крупными проектами подобные правила имеют смысл, даже если кажутся бесполезными и драконовскими?
aamonster
12.12.2024 16:07Обычно если сходу по "ёлочке" не видно начало блока – надо рефакторить, вынося куски кода в отдельные функции.
aabzel Автор
12.12.2024 16:07вынося куски кода в отдельные функции.
Да. Однако у нас в организации есть другое правило, которое блокирует этот процесс.
"Порядок объявления функций должен совпадать с порядком определения функций."Это подрывное правило исключает возможность создания большого количества маленьких Cи-функций, которые помещаются на один экран.
Дело в том что штатным программистам лень потом вручную сортировать функции по порядку объявления.
Из-за этого правила программисты предпочитают создавать 3, максимум 5 - мега функций-богов по тысяче строк в каждой, которые делают всё.
И это убивает модульность, читаемость, тесто пригодность и восприятие кода другими сотрудниками.
Это самый настоящий anti-pattern программирования.Да, вот так, господа...
randomsimplenumber
12.12.2024 16:07Порядок объявления функций должен совпадать с порядком определения функций
Дюра лекс конечно, но есть ли этому правилу непротиворечивое объяснение? Или это из тех домезозойских времён, когда текст программы нужно было распечатать на АЦПУ?
aabzel Автор
12.12.2024 16:07Уже обсуждали вот
https://habr.com/ru/articles/844436/#comment_27317592Автор требования по ходу аутист. Он и в офисе себя ведет странно.
К таким надо относиться с пониманием...
nin-jin
12.12.2024 16:07Один вместо использования инструментов, постоянно показывающих шапку блока на экране вводит правила по ее копированию в конец блока. Другой вместо написания тула автоматически добавляющего комментарии делает тул, который лишь показывает где их нет. И никого не смущают файлы на 5к строк и язык разработки из мезозоя. Я бы вас обоих даже джунами не взял, ибо тут наблюдается полная ментальная проф непригодность - программисты, не способные автоматизировать даже свою работу.
aabzel Автор
12.12.2024 16:07И язык разработки из мезозоя
Вы, Дмитрий, не забывайте, что это хаб программирования микроконтроллеров. А производители микроконтроллеров сами рекомендуют именно си для разработки.
nin-jin
12.12.2024 16:07Ну так и используйте современную его версию: https://habr.com/ru/articles/511334/
aabzel Автор
12.12.2024 16:07Боюсь мировое сообщество embedded разработчиков Вас (как front-end программиста) не поймет.
Если Вы не в курсе, то внутри микроконтроллеров ARM ядро. И для его запуска компания ARM дает CMSIS код, который написан на чистом Си.
Вот предложите британскому ARMу переписать CMSIS на D.
А потом напишите нам, что они Вам ответили.nin-jin
12.12.2024 16:07Я смотрю Вы (как представитель embedded разработчиков) традиционно поленились прочитать статью, от чего и высказываете глупые гипотезы о необходимости переписывания стороннего кода.
CitizenOfDreams
12.12.2024 16:07и язык разработки из мезозоя
Ну, в микроконтроллерах вариантов не особо много - или использовать язык из эпохи мезозоя, или для мигания светодиода придется ставить контроллер из эпохи Бака Роджерса.
Скрытый текст
DrGluck07
12.12.2024 16:07А "ардуинщики" именно так и делают, ставят какой-нибудь F429 чтоб мигать тремя светодиодами по команде с UART на скорости 9600.
aabzel Автор
12.12.2024 16:07Один вместо использования инструментов, постоянно показывающих шапку блока на экране вводит правила по ее копированию в конец блока.
Дело в том что тот человек, который придумывает у нас правила кодстайла обладает слишком низкой квалификацией, чтобы разбираться в инструментах и тем более в их разработке
CitizenOfDreams
12.12.2024 16:07тот человек, который придумывает у нас правила кодстайла обладает слишком низкой квалификацией, чтобы разбираться в инструментах и тем более в их разработке
Тогда возникают сомнения в квалификации человека, который поручил ему придумывать правила кодстайла. Мы же не позволяем людям, которые не разбираются в какой-то области деятельности, придумывать законы для нее? Wait...
aabzel Автор
12.12.2024 16:07Другой вместо написания тула автоматически добавляющего комментарии делает тул, который лишь показывает где их нет.
Ну знаешь...
Компилятор тоже не чинит найденные ошибки, а только лишь показывает факт наличия ошибок.aamonster
12.12.2024 16:07Компилятор – нет, а вот IDE предлагает автоматический фикс. Или вот eslint – у него есть флаг --fix, когда он записывает в файл предлагаемые правки (потом смотришь в git, что же он поменял, и коммитишь нужное).
Да и вашу тулзу не так уж трудно допилить, добавив такой ключик. Всё равно храните стек строк с открывающейся скобкой – никто не мешает оттуда строки брать и записывать в выходной файл.
DrGluck07
12.12.2024 16:07На уровне HAL используем C, в бизнес-логике C++. И производители микроконтроллеров тоже пишут библиотеки на C. Не на D, прошу заметить, не на Rust, не на Python, не на Go, и ни на чём другом. Вот таки мы странные ребята.
aabzel Автор
12.12.2024 16:07Да просто парень @nin-jin front-ender не туда зашёл по ошибке. Он не со зла.
CitizenOfDreams
12.12.2024 16:07не туда зашёл по ошибке
Наши сообщения об ошибках ему не понравятся...
Скрытый текст
randomsimplenumber
12.12.2024 16:07И чтобы таких вот ошибок не было, нужно подписывать закрывающие скобки комментариями и размещать функции в определенном порядке, правильно?
aabzel Автор
12.12.2024 16:07Не нахожу причинно-следственной связи.
nin-jin
12.12.2024 16:07Вы используете язык, переполненный UB, что приводит к непредсказуемому проведению программы, но вместо решения этой проблемы, играетесь с форматированием. И кто из нас фронтендер после этого?
aabzel Автор
12.12.2024 16:07Если что, то я вообще против любых комментариев к коду. Просто на работе заставляют их писать в огромном количестве.
Как по мне, хороший код понятен и без комментариев.
А упор надо делать на модульных тестахCitizenOfDreams
12.12.2024 16:07Как по мне, хороший код понятен и без комментариев.
Комментарии хранят информацию, за которой иначе пришлось бы куда-то лезть:
const unsigned char TMR0LoadValue=61; // 40Hz/25ms interrupt rate @1MHz & 1:32 prescaler FVRCON=0b10000001; // FVR and temp sensor; disable ADFVR before sleep for minimum power consumption! PRR=0b00001111; // Power Reduction Register, see PDF page 38
Позволяют напомнить себе будущему (или другому несчастному, который полезет в этот код), что именно происходит в данном месте:
if ((runMode==2)&&(distanceToGo==0)) // failed to trigger the sensor digitalWrite(ENA,LOW); // just in case, the driver is always on anyway digitalWrite(0,1); // data line high to prevent phantom powering the LEDs
Ну или просто так, чтоб было:
wdt_reset(); // woof!
aabzel Автор
12.12.2024 16:07const unsigned char TMR0LoadValue=61; // 40Hz/25ms interrupt rate @1MHz & 1:32 prescaler FVRCON=0b10000001; // FVR and temp sensor; disable ADFVR before sleep for minimum power consumption! PRR=0b00001111; // Power Reduction Register, see PDF page 38
Боже мой какой хардкод!
Не показывайте это никому... Прошу Вас.
Крайне рекомендую Вам ознакомится с этим текстом
https://habr.com/ru/articles/683762/
Архитектура Хорошо Поддерживаемого драйвера для I2C/SPI/MDIO Чипа
и Вы забудете, что такое комментарии в СиCitizenOfDreams
12.12.2024 16:07Боже мой какой хардкод!
Это не универсальный драйвер. Это программа, которая будет работать на одном железе, с одной частотой, с одними настройками таймеров. Предлагаете не хардкодить, а вынести все эти настройки - которые НИКОГДА не будут меняться - в отдельное место? Просто чтоб было по феншую?
randomsimplenumber
12.12.2024 16:07Иногда хочется запустить программу на другом железе. А переписывать не хочется.
CitizenOfDreams
12.12.2024 16:07Иногда хочется запустить программу на другом железе. А переписывать не хочется.
А придется. Или переписывать, или изначально писать программы, учитывающие все возможные варианты железа. И вместо "FVRCON=0b10000001" будет три конфигурационных файла, пять библиотек и десять трудноуловимых багов. И все равно в итоге придется что-то исправлять.
randomsimplenumber
12.12.2024 16:07Или переписывать, или изначально писать программы, учитывающие все возможные варианты железа.
Или максимально абстрагироваться от железа, а железо-зависимые константы выносить в отдельный файл. Или использовать framework, где привязка к железу уже сделана (Arduino, ага ;)).
aabzel Автор
12.12.2024 16:07Предлагаете не хардкодить, а вынести все эти настройки - которые НИКОГДА не будут меняться - в отдельное место? Просто чтоб было по феншую?
Надо придерживаться методологии
код отдельно, конфиги отдельно.
Это требование ISO26262
Вот текст про это
ISO 26262-6 разбор документа (или как писать безопасный софт)
https://habr.com/ru/articles/757216/
aabzel Автор
12.12.2024 16:07Надо не hardcode(ить) битовые константы как тут (Боже упаси...)
const unsigned char TMR0LoadValue=61; // 40Hz/25ms interrupt rate @1MHz & 1:32 prescaler FVRCON=0b10000001; // FVR and temp sensor; disable ADFVR before sleep for minimum power consumption! PRR=0b00001111; // Power Reduction Register, see PDF page 38
а создавать константные битовые поля и присваивать им именованные перечисления (слыхали про enum в Си?).
Тогда и // /**/ комменты нужны будут как собаке бензобак.
Понимаете?
Вот так надоconst Ltr390Register_t Ltr390Register[]={ { .address=LTR390_REG_ADDR_MEAS_RATE, .value.MeasRate={ .rate=LTR390_RATE_25_MS, .resolution=LTR390_RESOLUTION_CODE_20_BIT, }, }, { .address=LTR390_REG_ADDR_GAIN, .value.AlsUvsGain={ .gain=LTR390_GAIN_CODE_18}, }, };
Почитайте про битовые поля в Си. Узнаете что это такое.CitizenOfDreams
12.12.2024 16:07а создавать константные битовые поля и присваивать им именованные перечисления
Нафига? Данная битовая константа берется один раз из даташита и в ходе разработки данной прошивки не меняется. Это все равно что номер квартиры на двери сделать не двумя жестяными цифрами, прикрученными шурупами, а в виде электронного табло, на котором можно задавать числа от -32768 до +32767.
aabzel Автор
12.12.2024 16:07Вы используете язык, переполненный UB, что приводит к непредсказуемому проведению программы
У компилятора GCC так много ключей для ловли UB что достаточно только собрать бинарь с пучком опций
-Werror=address -Werror=switch -Werror=array-bounds=1 -Werror=comment -Werror=div-by-zero -Werror=duplicated-cond -Werror=shift-negative-value -Werror=duplicate-decl-specifier -Werror=enum-compare -Werror=uninitialized -Werror=empty-body -Werror=unused-but-set-parameter -Werror=unused-but-set-variable -Werror=float-equal -Werror=logical-op -Werror=implicit-int -Werror=implicit-function-declaration -Werror=incompatible-pointer-types -Werror=int-conversion -Werror=old-style-declaration -Werror=maybe-uninitialized -Werror=redundant-decls -Werror=sizeof-pointer-div -Werror=misleading-indentation -Werror=missing-declarations -Werror=missing-parameter-type -Werror=overflow -Werror=parentheses -Werror=pointer-sign -Werror=return-type -Werror=shift-count-overflow -Werror=strict-prototypes -Werror=unused-but-set-variable -Werror=unused-function -Werror=missing-field-initializers -Werror=unused-variable -Werror=unused-but-set-variable -Werror=implicit-function-declaration -Werror=unused-variable
и всё обычно хорошо. По крайней мере проблем никогда не было.
Старость Си это наоборот достоинство так как именно поэтому появились очень зрелые компиляторы. Даже бесплатный ARM-GCC 2021 года.nin-jin
12.12.2024 16:07int main(void) { int arr[4] = {0, 1, 2, 3}; return arr[5]; }
https://godbolt.org/z/xKTPz4zzh
Мне страшно даже представить, сколько в вашем коде ещё не обнаруженных уязвимостей.
aabzel Автор
12.12.2024 16:07Модульные тесты проходят - значит всё хо-ро-шо.
nin-jin
12.12.2024 16:07Значит вы завязались на неопределённое поведение, которое может сломаться в любой момент. Дико осознавать, что фронтендер вынужден объяснять всё это сишнику.
Для справки, эквивалентный код на BetterC даже не скомпилируется:
extern(C) int main() { int[4] arr = [0, 1, 2, 3]; return arr[5]; }
Error: array index 5 is out of bounds `arr[0 .. 4]`
А даже если перенесёте получение индекса в рантайм, то получите падение, а не чтение чего попало из памяти:
extern(C) int main() { int[4] arr = [0, 1, 2, 3]; int x = 5; return arr[x]; }
Assertion `array overflow' failed.Program terminated with signal: SIGSEGV
aabzel Автор
12.12.2024 16:07и язык разработки из мезозоя
Почему микроконтроллеры надо программировать именно на Си?
Какие есть варианты языков для программирования микроконтроллеров? Теоретически подойдет любой компилируемый язык программирования: Assembler, Cи, С++, Rust, Pascal
Однако выбирают обычно между Си и С++
1++Cи язык очень легко подается рефакторингу. Переименовывать функции, константы и переменные можно буквально утилитой sed. Прямо из командной строки внутри всего репозитория. При этом код по-прежднему будет собираться и проходить тесты.
2++У Си есть циклопическое Legacy. Нужный вам код можно взять из ядра Linux или Zephyr.
3++Программировать на Си проще чем программировать на Assembler или С++
4++Высокая скорость разработки. В сравнении с Assembler программирование на Си позволяет повысить производительность.
5++Это компилированный язык. Значит он будет исполняться быстрее чем интерпретированный язык.
6++Есть указатели.
7++Есть слово volatile
8++У компилятора GCC очень много ключей для гибкой настройки различных видов предупреждений на тот или иной синтаксис и семантику.
9++Вендоры микроконтроллеров дают MCAL именно на Си. Это сотни человеко-лет готовой работы. Поэтому чтобы сэкономить время и использовать их код надо тоже продолжать писать на Си.
10++Существует бесплатный компилятор GCC
11++Язык Си простой как ножик. Одни только функции и переменные. Прост в освоении. Тут не будет виртуальных деструкторов, делегатов, шаблонов и прочего. Код выглядит как математические формулы, которые и так все видели в школе.
12++Достоинство языка Си в том, что он старый 50+ лет и поэтому появились очень зрелые компиляторы. В частности в GCC появилось много опций для выявления UB.
Чего не хватает?
1--перегрузки функций. Хотя с этой задачей нормально справляются макросы препроцессора.
2--знаковых типов данных произвольной разрядности int13_t int7_t. Но этого и так нигде нет.
Итоги
Си- это идеальный язык для программирования микроконтроллеров. Компромисс между эффективностью и простотой.
gefestik
12.12.2024 16:07кстати, разработали компилятор Fli-C, который помогает управлять безопасной работой памяти https://www.opennet.ru/opennews/art.shtml?num=62241
vadimr
По-хорошему надо ещё лексический контекст учитывать.
Если в вашем исходнике поменять местами
case '{'
иcase '}'
и подать его на вход самому себе, то ему не захорошеет (хотя он и так нарушает проверяемое им же самим правило).А если совсем строго, то надо раскрывать макроподстановки.
Фортран своими руками.
Jijiki
поидее если считаем { +1 а } -1(где 1 - это 2 пробела допустим), но если учесть функционал что просто скобочку на новую строку поставить(если открывающая скобочка в конце строки ) и попутно удаляя табы перед строкой и в конце строки, а код при этом компилируется, то поидее не плохой форматер выходит, а так да надо поидее все токены ловить и по новой простраивать как я понимаю по хорошему
RomanDrDev
Хороший совет.
А еще я при парсинге файлов конфигурации 1С (которые в SQL в dbo.Config лежат, а в файловых базах в таблицах .dt файла) сталкивался с ситуацией, когда в комментариях к реквизиту/объекту была открывающая фигурная скобка, но не было закрывающей. И это тоже ломало CLR сборку, пока не добавил в нее обработку комментариев.
aamonster
Не надо раскрывать макроподстановки. Если макросы нарушают баланс скобок или делают ещё что-то, что помешает работе такого анализатора – то они помешают и человеку читать код, а значит, должны быть переписаны.
vadimr
Допустим, могут быть через макросы определены свои операторные скобки для какой-то цели. Типа, начало какого-то контекста и конец контекста. Первый макрос будет включать {, а второй - }.
aamonster
Соответственно, эти макросы всегда будут идти парами, и баланс скобок не нарушится. Приемлемо.
vadimr
Если в программе нет ошибок.
aamonster
Да. Причём грубых, которые можно обнаружить автоматически.