В некоторых российских организациях есть внутренний стандарт оформления исходных кодов на языке программирования Си. В частности и у нас тоже есть много требований к внешнему виду кода.
У меня уже был текст Синхронизация порядка объявлений и определений глобальных функций. Однако аналогичная история также актуальна для static функций. Среди 463х правил внутреннего code style у нас есть и такое обязательное требование к оформлению кода:
Последовательность объявления static функций должна совпадать с последовательностью определения static функций в конце *.с файла.
Я подозреваю, всем здесь понятно, что, если бы static си-функции были определены в начале си-файла, то этого наркоманского правила бы вообще не существовало в принципе. Однако здесь вам не тут! У нас также есть ещё одно нелепое правило
Все static функции должны быть определены в самом низу *.с файла.
В связи с этим, чтобы код вообще собирался теперь приходится ещё и искусственно объявлять повторно прототипы этих самых static функций в самом начале *.с файла. Нормально так да? А раз так, то пусть тогда и порядок совпадает. Цирк с конями, да и только... Это что?
Это ярчайший пример того, как буквально на пустом месте можно создать проблем себе и коллегам в колёса на много-много лет вперёд. Это классический пример ничем не обоснованной IT муштры.
Дело ясное, что поведение кода будет одним и тем же, что отсортированы static прототипы что не отсортированы, погоду это не изменит. Компилятору это всё равно. Модульные тесты (если они есть) в любом случае будут проходить одинаково.
Можно подумать, что нам тут ну просто больше заняться не чем, кроме как перетасовывать местами определения функций. Бантики какие-то привязывать, вместо реальной работы.
У нас нет ни одного модульного теста. Мы собирает код в Arduino-образной IDE, У нас в репозитории одна единственная сборка-франкенштейн сразу для трех разных электронных плат, у нас нет UART-CLI, у нас в прошивке нет загрузчика, у нас в прошивке нет NVRAM. Зато мы заботимся о том, чтобы пресловутый прядок объявления static функций совпадал с последовательностью определения static функций в конце *.с файла. Это как?
Как там говорят: "Один ноль в пользу Байдона"?
Если исключить теорию заговора и подрывные диверсии сверху, то тогда это получается обыкновенная глупость. Те, кто придумывает такого рода требования к коду просто не понимает причинно-следственную связь в программировании. Не понимает, какие усилия приведут к результату, а какие нет.
Однако правило такое тут существует, и чтобы фиксации с кодом прошили цензуру на Gerrit(е) и попали в общак, как ни крути, надо это требование исполнять.
В чем проблема?
Проблема ещё и в том, что тот интеллектуальный сотрудник, который нафантазировал все эти правила, поленился накропать утилиты для автоматического контроля всего своего этого законотворчества. Наверное в случае разработки утилит-надзирателей его пыл бы быстро подугас.
Однако правило обязательное и с этим, как ни крути, приходится жить.
Любая разработка начинается только тогда, когда появляются полноценные средства для отладки.
Очевидно же, что вручную выслеживать эти нарушения порядка объявлений и определений — это работа адовая! Очень утомительно, рутинно, долго и дорого. Поэтому программисты у нас 12 лет лелеяли мечту на появление волшебной палочки: консольной программы, которая автоматически находит эти пресловутые нарушения порядка static объявлений.
И, как водится, такой утилиты в открытом доступе до сегодняшнего дня не существовало в природе, подобно тому как чистого плутония не было в земной коре до появления первых рукотворных атомных реакторов.
Поэтому я и написал такую утилиту-локатор. Назвал её prototype_check с ключом csp. Пришлю утилиту всем коллегам по несчастью.
Что надо из софтвера?
Вам надо будет установить CygWin чтобы извлечь из него вот эти утилиты:
# |
Название утилиты |
Для чего эта утилита |
1 |
awk / gawk |
анализатор и обработчик csv строчек |
2 |
winMerge |
Сравнение текстовых файлов |
3 |
sed |
удаление и замена текста в файле |
4* |
ctags |
извлекатор текстовых токенов из исходников в разных языках программирования |
5 |
cmp |
утилита сравнения файлов |
Каков план?
Я предлагаю реализовать вот такой программный конвейер. Всего 4 стадии. Фронт работ такой:
Реализация
Ядром всего этого решения является старинная утилита ctags. У ctags тут главная роль. Поясним, те опции утилиты ctags, которые нам понадобятся в этом решении здесь и сейчас.
Опция ctags |
Расшифровка |
-x --c-types=f |
извлечь из *.с файла только функции |
-x --c-kinds=p |
извлечь из *.с файла прототипы функций |
--sort=no |
не сортировать в отчёте найденные токены (функций) |
-fcTagReport.txt |
сохранить отчет в файл сTagReport.txt |
А теперь пояснения к алгоритму работы утилиты. Итак, танцуем от печки...
Фаза 1: Показать все функции в *.c файле
Чтобы показать только функции надо написать
ctags.exe --sort=no -fcTagReport.txt -x --c-types=f i2c_drv.c
Кристаллизовался вот такой сырой лог
Скрытый текст
I2C_Init function 783 C:\projects\source\I2C\i2c_drv.c STD_RESULT I2C_Init(void)
I2C_DeInit function 896 C:\projects\source\I2C\i2c_drv.c STD_RESULT I2C_DeInit(void)
I2C_StartReading function 968 C:\projects\source\I2C\i2c_drv.c STD_RESULT I2C_StartReading(const U8 nBus,
I2C_HighLevel_RX_ISR function 1758 C:\projects\source\I2C\i2c_drv.c void I2C_HighLevel_RX_ISR(const U8 nBus)
I2C_HighLevel_TX_ISR function 1775 C:\projects\source\I2C\i2c_drv.c void I2C_HighLevel_TX_ISR(const U8 nBus)
I2C_HighLevel_Error_ISR function 1932 C:\projects\source\I2C\i2c_drv.c void I2C_HighLevel_Error_ISR(const U8 nBus)
I2C_LL_GPIOInit function 2048 C:\projects\source\I2C\i2c_drv.c static void I2C_LL_GPIOInit(const U8 nArgBus)
I2C_LL_GPIODeInit function 2144 C:\projects\source\I2C\i2c_drv.c static void I2C_LL_GPIODeInit(const U8 nArgBus)
I2C_SetConfigTransfer function 2246 C:\projects\source\I2C\i2c_drv.c static STD_RESULT I2C_SetConfigTransfer(const I2C_BUS nArgBus,
I2C_SendData function 2417 C:\projects\source\I2C\i2c_drv.c static STD_RESULT I2C_SendData(const I2C_BUS nArgBus,
I2C_ReceiveData function 2470 C:\projects\source\I2C\i2c_drv.c static STD_RESULT I2C_ReceiveData(const I2C_BUS nArgBus,
I2C_ResetCtrl2Register function 2527 C:\projects\source\I2C\i2c_drv.c static STD_RESULT I2C_ResetCtrl2Register(const I2C_BUS nArgBus)
I2C_ClearFlags function 2593 C:\projects\source\I2C\i2c_drv.c static STD_RESULT I2C_ClearFlags(const I2C_BUS nArgBus,
I2C_CheckFlag function 2661 C:\projects\source\I2C\i2c_drv.c static STD_RESULT I2C_CheckFlag(const U8 nBus,
I2C_SetInterruptState function 2728 C:\projects\source\I2C\i2c_drv.c static STD_RESULT I2C_SetInterruptState(const U8 nArgBus,
I2C_RefreshTxdtRegister function 2783 C:\projects\source\I2C\i2c_drv.c static void I2C_RefreshTxdtRegister(I2C_tag* const pArgHw)
I2C_ConversionCfgIndex function 2810 C:\projects\source\I2C\i2c_drv.c static STD_RESULT I2C_ConversionCfgIndex(const U8 nArgBus,
Фаза 2: Удалить все не static строчки
В отчете ctags надо оставить только static функции. Или надо удалить все строки, которые не содержат ключевого слова static. Это можно сделать консольной утилитой sed
sed -i '/ static /!d' cTagReport.txt
Остались только определения static функций. Получается вот такой лог
Скрытый текст
I2C_LL_GPIOInit function 2048 C:\projects\source\third_party\I2C\i2c_drv.c static void I2C_LL_GPIOInit(const U8 nArgBus)
I2C_LL_GPIODeInit function 2144 C:\projects\source\third_party\I2C\i2c_drv.c static void I2C_LL_GPIODeInit(const U8 nArgBus)
I2C_SetConfigTransfer function 2246 C:\projects\source\third_party\I2C\i2c_drv.c static STD_RESULT I2C_SetConfigTransfer(const I2C_BUS nArgBus,
I2C_SendData function 2417 C:\projects\source\third_party\I2C\i2c_drv.c static STD_RESULT I2C_SendData(const I2C_BUS nArgBus,
I2C_ReceiveData function 2470 C:\projects\source\third_party\I2C\i2c_drv.c static STD_RESULT I2C_ReceiveData(const I2C_BUS nArgBus,
I2C_ResetCtrl2Register function 2527 C:\projects\source\third_party\I2C\i2c_drv.c static STD_RESULT I2C_ResetCtrl2Register(const I2C_BUS nArgBus)
I2C_ClearFlags function 2593 C:\projects\source\third_party\I2C\i2c_drv.c static STD_RESULT I2C_ClearFlags(const I2C_BUS nArgBus,
I2C_CheckFlag function 2661 C:\projects\source\third_party\I2C\i2c_drv.c static STD_RESULT I2C_CheckFlag(const U8 nBus,
I2C_SetInterruptState function 2728 C:\projects\source\third_party\I2C\i2c_drv.c static STD_RESULT I2C_SetInterruptState(const U8 nArgBus,
I2C_RefreshTxdtRegister function 2783 C:\projects\source\third_party\I2C\i2c_drv.c static void I2C_RefreshTxdtRegister(I2C_tag* const pArgHw)
I2C_ConversionCfgIndex function 2810 C:\projects\source\third_party\I2C\i2c_drv.c static STD_RESULT I2C_ConversionCfgIndex(const U8 nArgBus,
Фаза 3: Из отчёта надо выделить только первую колонку
Выделять колонки из текстовых файлов можно утилитой awk. Вот такой командой.
gawk '{print $1}' cTagReport.txt > definitions.txt
Фаза 4: Извлечь прототипы функций
Чтобы извлечь из src.c в cTagReport.txt только прототипы надо выполнить вот такую команду
ctags --sort=no -fcTagReport.txt -x --c-kinds=p src.c
Получится вот такой файл со списком прототипов.
Скрытый текст
I2C_LL_GPIOInit prototype 719 C:\projects\code_base_firmware\source\I2C\i2c_drv.c static void I2C_LL_GPIOInit(const U8 nArgBus);
I2C_LL_GPIODeInit prototype 722 C:\projects\code_base_firmware\source\I2C\i2c_drv.c static void I2C_LL_GPIODeInit(const U8 nArgBus);
I2C_SetConfigTransfer prototype 725 C:\projects\code_base_firmware\source\I2C\i2c_drv.c static STD_RESULT I2C_SetConfigTransfer(const I2C_BUS nArgBus,
I2C_SendData prototype 730 C:\projects\code_base_firmware\source\I2C\i2c_drv.c static STD_RESULT I2C_SendData(const I2C_BUS nArgBus,
I2C_ReceiveData prototype 734 C:\projects\code_base_firmware\source\I2C\i2c_drv.c static STD_RESULT I2C_ReceiveData(const I2C_BUS nArgBus,
I2C_ResetCtrl2Register prototype 738 C:\projects\code_base_firmware\source\I2C\i2c_drv.c static STD_RESULT I2C_ResetCtrl2Register(const I2C_BUS nArgBus);
I2C_ClearFlags prototype 741 C:\projects\code_base_firmware\source\I2C\i2c_drv.c static STD_RESULT I2C_ClearFlags(const I2C_BUS nArgBus,
I2C_CheckFlag prototype 745 C:\projects\code_base_firmware\source\I2C\i2c_drv.c static STD_RESULT I2C_CheckFlag(const U8 nBus,
I2C_SetInterruptState prototype 750 C:\projects\code_base_firmware\source\I2C\i2c_drv.c static STD_RESULT I2C_SetInterruptState(const U8 nArgBus,
I2C_RefreshTxdtRegister prototype 755 C:\projects\code_base_firmware\source\I2C\i2c_drv.c static void I2C_RefreshTxdtRegister(I2C_tag* const pArgHw);
I2C_ConversionCfgIndex prototype 758 C:\projects\code_base_firmware\source\I2C\i2c_drv.c static STD_RESULT I2C_ConversionCfgIndex(const U8 nArgBus,
Фаза 5: Из отчета прототипов выделить только имена функций
Теперь надо выделить только имена функций. Это по сути первый столбец. Такие задачи решаются культовой утилитой awk
gawk '{print $1}' cTagReport.txt > declarations.txt
Внутри текстового фaйла declarations.txt кристаллизовался список функций так, как он представлен в части, где происходит объявления прототипов static функций.
Фаза 6: сравнить списки функций
На этом этапе задача свелась к обыкновенному сравнению двух текстовых файлов. По идее они должны быть одинаковые, как две капли воды. Сравнивать текстовые файлы можно утилитой cmp
cmp -s declarations.txt definitions.txt
Результат сравнения и покажет аномалии.
Вот полный скрипт который делает проверку
cls
echo off
set srcFile=some_driver.c
set cTagFile=cTagReport.txt
set StativRepDef=StativRepDef.txt
set StativRepDec=StativRepDec.txt
rm -f %cTagFile%
rm -f %StativRepDef%
rm -f %StativRepDec%
set options=--sort=no
set options=%options% -f%cTagFile%
set options=%options% -x --c-types=f
ctags.exe %options% %srcFile%
sed -i '/ static /!d' %cTagFile%
gawk '{print $1}' %cTagFile% > %StativRepDef%
rm -f %cTagFile%
set options=--sort=no
set options=%options% -f%cTagFile%
set options=%options% -x --c-kinds=p
ctags %options% %srcFile%
gawk '{print $1}' %cTagFile% > %StativRepDec%
cmp -s %StativRepDec% %StativRepDef%
echo errorlevel=%errorlevel%
if "%errorlevel%"=="0" (echo same) else (echo diff)
Так как скрипт нужен очень часто, то я инкапсулировал его в консольную утилиту prototype_check. Эта утилита некоторый аналог утилиты BusyBox. По ключу вызываются другие утилиты. Скрипт проверки static прототипов будет вызываться по ключу csp (check static prototype).
Отладка
Вот так выглядит лог утилиты в случае успеха
А вот такой лог будет в случае отрицательного успеха
Утилита prototype_check производит тщательный контроль создает два текстовых файла, чтобы увидеть недочёт. Показывает какие именно static функции обитают не на своём месте. Tool(а) в самом деле обнаружила рассинхрон в определениях. Это наглядно видно утилитой WinMerge.
Таким образом программист получает целеуказание на те static функции, которые надо так или иначе подвинуть. Теперь понятно что делать.
Итоги
В сухом остатке, мне удалось разработать уникальную утилиту-надзиратель, которая как лакмусовая бумажка автоматически показывает потери синхронизации между порядком объявления и порядком определения static функций в *.с файле на языке программирования Си.
Любо братцы жить
С нашей утилитой не приходится тужить
2x
Надеюсь, что утилита prototype_check или её версия в виде скрипта помогут другим программистам микроконтроллеров, которые тоже работают в условиях диктатуры внутреннего стандарта оформления программных текстов.
Если нужна эта утилита обращайтесь.
Словарь
акроним |
Расшифровка |
CSV |
Comma-separated values |
csp |
Check Static Prototyes |
Ссылки
Комментарии (20)
Daemonis
25.09.2024 22:16Утилита prototype_check производит тщательный контроль создает два текстовых файла, чтобы увидеть недочёт. Показывает какие именно static функции обитают не на своём месте.
Непонятно, зачем останавливаться на полпути :) Нужно создавать diff файл, который можно применить (возможно, прямо самой утилитой) и исправить проблему.
aabzel Автор
25.09.2024 22:16Это очень сложно.
randomsimplenumber
25.09.2024 22:16Переставить строки в файле сложно, если у вас 8 битный контроллер.
9241304
25.09.2024 22:16+1Это которая по счету статья о корпоративных костылях от этого автора? Хотелось бы уже узнать название этой чудесной конторы)
aabzel Автор
25.09.2024 22:16Почему именно "костыль"?
Можно подумать, что существует какая-то широко известная утилита, которая проверяет порядок объявления и определения static функций в Си коде.
Я, между прочим, исследование проделал. И пришёл к выводу, что такой утилиты не существует.Поэтому и пришлось мне ее написать.
9241304
25.09.2024 22:16+2Надеюсь, это был сарказм. Если нет, то поясняю: этого нет, потому что это никому не нужно, кроме "организаций", где важно правильно переложить бумажку, а не сделать продукт.
Всё же мой вопрос был про название этой чудесной "организации". Ответите? )
randomsimplenumber
25.09.2024 22:16+2такой утилиты не существует.
Потому что за пределами вашей конторы она никому не нужна?
aabzel Автор
25.09.2024 22:16Я это наркоманское правило видел аж в трех российских конторах. Как-будто по методичке составлено.
randomsimplenumber
25.09.2024 22:16+1Своеобразный дресс код для программистов;) Может, у этих контор есть что то общее?
aabzel Автор
25.09.2024 22:16Может, у этих контор есть что то общее?
Да, В названии всех этих компаний куча заглавных букв. Они получают заказы от МинПромТорг(а).
randomsimplenumber
25.09.2024 22:16+1Ну кто девушку ужинает, то ее и танцует (ц). Требование заказчика.
AMDmi3
Не могу одного понять: если вы считаете это правило ничем не обоснованной IT муштрой, и не было утилиты которая его проверяет и энфорсит, зачем было нужно её писать вместо того чтобы правило игнорировать и саботировать?
aabzel Автор
del
aabzel Автор
Потому что могу.
randomsimplenumber
Как задача из литкода.
aabzel Автор
Какой номер задачи на leetcode соответствует этой теме?
randomsimplenumber
Что-то из уровня easy.
aabzel Автор
Пришлите ,пожалуйста, ссылку.
aabzel Автор
Саботирует работу как раз тот, кто придумывает все эти нелепые правила code style.
aabzel Автор