Pull to refresh

Стилистический анализатор: синхронизация объявлений и определений static функций

Level of difficultyEasy
Reading time9 min
Views1.8K

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

У меня уже был текст Синхронизация порядка объявлений и определений глобальных функций. Однако аналогичная история также актуальна для 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

Ссылки

Only registered users can participate in poll. Log in, please.
У вас в организации есть требование: «Последовательность объявления static функций должна совпадать с последовательностью определения static функций в конце *.с файла.»
11.11% да1
88.89% нет8
9 users voted. 4 users abstained.
Tags:
Hubs:
Total votes 7: ↑5 and ↓2+7
Comments21

Articles