👋Это демо-отчёт на синтетическом проекте — чтобы показать, что вы получите на выходе. Свой код грузить не нужно. Хотите проверить свой?
🔎1 POU на графических языках (CFC 1) — построчные проверки к ним НЕ применялись; имена покрыты частично (вхождения в блоках/выражениях). Это ограничение инструмента, а не признак чистоты этих POU.
0
Оценка качества: 0/100 Сохраните отчёт в профиль — соберётся подтверждённый трек качества. Код не сохраняется, только агрегат.
У каждого правила есть кнопка шум? — нажмите её, если это правило даёт нерелевантные срабатывания на вашем коде. Сигнал помогает нам подстроить проверки: понизить важность, сузить условие или отключить лишнее. Ваш код при этом не отправляется.
Критично 10 шт.
CFC: деление на литеральный ноль1 · cfc-div-zero
В графике (CFC/FBD) делитель блока DIV/MOD подключён к литеральной константе 0 — рантайм-исключение при исполнении блока. Проверяется по графу связей.
Варианты решения
Подайте на вход делителя переменную/уставку вместо константы 0 — обычно это забытая заглушка после отладки.
Если делитель может обнуляться в рантайме — поставьте перед DIV/MOD выбор безопасного значения (SEL по сравнению с 0).
CfcDiv
— делитель блока DIV подключён к литеральному 0 → гарантированное деление на ноль
Функция неявного контроля не защищает1 · broken-implicit-check
Функция неявного контроля (Check…) есть, но её защитная логика выхолощена: пустое тело или убрана проверка (делителя на 0 / границ). Рантайм вызывает её, но она ничего не ловит — ложное чувство безопасности, опаснее её отсутствия.
Варианты решения
Верните защитную логику: CheckDivReal должен проверять IF divisor = 0 THEN … := 1;
CheckBounds/CheckRange… должны зажимать значение в lower..upper. Сверьтесь с дефолтной реализацией CODESYS.
CheckBounds
— нет сравнения с границами lower/upper — значение не ограничивается — защита есть, но не работает (ложное чувство безопасности)
Деление на литеральный ноль2 · div-by-zero
Деление или MOD на литеральный ноль — гарантированное исключение исполнения (division by zero) при заходе в эту строку.
Варианты решения
Уберите деление на 0 — вероятно, опечатка в делителе.
Если делитель бывает нулём динамически — защитите: IF d <> 0 THEN r := x / d; END_IF
DivZeroVar:1
— деление на znever — переменная = 0 (инициализатор 0/пустой) и нигде не присваивается → гарантированное деление на ноль
qa := 5 / znever;
Main:2
— деление/MOD на литеральный ноль
d := d / 0;
Опечатка в имени глобала (gvl.*)1 · undeclared-global
Ссылка gvl.<имя> не находит объявления в GVL — почти всегда опечатка; переменная молча создаётся или код не компилируется.
Варианты решения
Сверьте написание с объявлением в GVL (регистр, подчёркивания, цифры).
Если переменная действительно нужна — добавьте её в GVL с типом и комментарием.
Main
— gvl.missing — нет в GVL (вероятная опечатка)
Индекс за границами массива1 · array-bounds
Константный индекс выходит за объявленные границы ARRAY[lo..hi] — заход в эту строку гарантированно читает/пишет чужую память. Без активных неявных проверок рантайм это не перехватит. Проверяются одномерные массивы с числовыми границами и литеральным индексом.
Варианты решения
Приведите индекс в диапазон объявления или расширьте границы массива.
Перед доступом проверяйте: IF idx >= LO AND idx <= HI THEN arr[idx] … END_IF
Main:1
— arr[5] вне границ [0..3]
arr[5] := 1;
Бесконечный цикл без выхода1 · infinite-loop
WHILE TRUE / REPEAT … UNTIL FALSE без EXIT и RETURN внутри — скан никогда не завершится: задача блокируется и срабатывает сторожевой таймер (а если он выключен — встаёт контроллер). В ПЛК длинную обработку разносят по сканам, а не крутят в цикле.
Варианты решения
Добавьте условие выхода и EXIT, либо ограничьте счётчиком итераций.
Тяжёлую обработку разбивайте по сканам — бесконечный цикл роняет watchdog задачи.
Main:11
— WHILE TRUE без EXIT/RETURN
Канал не заполняется — пропуск в семействе2 · family-gap
Поле объявлено у всех каналов семейства (котлы 1–4, насосы 1–3), используется у всех, кроме ровно одного — у него не заполняется никогда. Типовая причина: блок скопировали с соседнего канала и не дописали. Компилятор молчит: переменная существует, тип верный, просто в неё никто не пишет. На объекте это выглядит как «по котлу 4 не приходят аварии», причём остальные три работают, и на стенде дефект не воспроизводится. Правило опирается на объявления: если поле у канала не объявлено, значит там другое оборудование — это не пропуск, и находки не будет.
Варианты решения
Проверьте блок этого канала: скорее всего строка присваивания скопирована с соседнего и в ней остались его индексы — gvl.kotel_3_alarms3 := ... вместо gvl.kotel_4_alarms3 := ....
Если поле у этого канала не нужно (нет такого оборудования) — уберите его объявление, тогда и правило замолчит, и в GVL не останется мёртвой переменной.
ktr_errors
— поле kotel_4_alarms3 объявлено, но у канала 4 нигде не заполняется — у каналов 1, 2, 3 оно заполняется; похоже, блок скопировали с соседнего канала и не дописали
ktr_errors
— поле kotel_4_code_error_3 объявлено, но у канала 4 нигде не заполняется — у каналов 1, 2, 3 оно заполняется; похоже, блок скопировали с соседнего канала и не дописали
Параметр канала получает чужой канал1 · param-channel-mismatch
В вызове функционального блока имя параметра говорит про один канал, а значение взято из другого: `bPump2_Fault := gvl.alarm_nasos_CO1`. Правило срабатывает только когда в этом же вызове нумерация каналов доказана — есть согласованные пары минимум для двух каналов. Последствие тихое и опасное: аварийный насос считается исправным и выбирается для работы, защита не срабатывает, а по журналу всё выглядит штатно.
Варианты решения
Исправьте индекс в значении: bPump2_Fault := gvl.alarm_nasos_CO2.
Повторяющиеся вызовы по каналам лучше сворачивать в цикл по массиву (FOR i := 1 TO 3 DO fb[i](...)) — тогда перенумеровывать нечего.
work_nasos_CO:2
— канал 2 получает значение канала 1: в вызове fbSeize имя параметра говорит про канал 2, а сигнал взят из 1
bPump2_Fault := gvl.alarm_nasos_CO1
Внимание 10 шт.
Деление на литеральный ноль1 · div-by-zero
Деление или MOD на литеральный ноль — гарантированное исключение исполнения (division by zero) при заходе в эту строку.
Варианты решения
Уберите деление на 0 — вероятно, опечатка в делителе.
Если делитель бывает нулём динамически — защитите: IF d <> 0 THEN r := x / d; END_IF
DivZeroVar:3
— деление на zcond — переменной присваивается 0 на одной из веток и нет защиты zcond <> 0 → возможное деление на ноль
qb := 5 / zcond;
Присваивание переменной самой себе1 · self-assignment
Строка вида `x := x;` — присваивание переменной самой себе: не делает ничего. Обычно опечатка в правой части (имелась в виду другая переменная) или остаток от удалённой логики.
Варианты решения
Вероятно, справа должна быть другая переменная (опечатка) — проверьте намерение.
Если строка лишняя — удалите её.
Main:6
— присваивание самой себе — нет эффекта
x := x;
Пустая ветвь IF/цикла1 · empty-branch
Ветвь IF/ELSIF/ELSE или тело цикла пусты (`THEN END_IF`, `DO END_FOR`) — логику либо не дописали, либо вырезали при правке и забыли убрать конструкцию.
Варианты решения
Заполните ветвь логикой или удалите пустой IF/цикл.
Если пусто намеренно — оставьте комментарий-пояснение.
Main:7
— пустой IF … THEN … END_IF
CASE без ветви ELSE (нет default)1 · case-without-else
CASE без ветви ELSE: если селектор примет значение, которого нет среди меток, не выполнится ничего — молчаливый пропуск (частый баг стейт-машин).
Варианты решения
Добавьте ветвь ELSE с обработкой по умолчанию (сигнал/лог/безопасное состояние).
Если все значения селектора точно покрыты — оставьте ELSE с комментарием-пояснением.
Main:8
— CASE без ELSE — неучтённые значения селектора молча игнорируются
Строка расставляет индексы иначе, чем группа1 · index-mismatch
В группе однотипных строк отношение между позициями индексов держится почти везде, а в одной строке нарушено: одиннадцать строк пишут в свой канал, двенадцатая — в чужой. Так выглядит копипаст соседней ветки с неполной перенумерацией. Правило не сравнивает строки целиком: совпадение чисел из разных измерений (номер котла и номер блока аварий) — норма, и оно учтено. Признак эвристический: перед правкой посмотрите, не является ли эта строка осознанным исключением.
Варианты решения
Сравните строку с соседними по группе: чаще всего не перенумерован приёмник — w_cascadK4 := r_cascadK4 вместо w_cascadK3 := r_cascadK4.
Если строка — осознанное исключение из общего правила, добавьте комментарий рядом: следующему читателю (и вам через полгода) это сэкономит разбирательство.
reindex_cascad:14
— позиция 1 и позиция 2 совпадают в 11 строках группы из 12, а здесь различаются (3 против 4) — похоже, строку скопировали и перенумеровали не полностью
IF gvl.stop_K3 THEN gvl.w_cascadK4 := gvl.r_cascadK4; END_IF
Проверка присутствует, но не активна3 · implicit-check-disabled
Функция контроля есть в проекте, но не активна: либо не зарегистрирована (нет метки CheckFunction — рантайм её не вызывает), либо для целой категории (границы/деление/диапазон/указатели) нет ни одной активной проверки.
Варианты решения
Перегенерируйте POUs for Implicit Checks штатным диалогом CODESYS — так проверка получит регистрацию CheckFunction и будет вызвана рантаймом.
Добавьте недостающую категорию (границы/деление/диапазон/указатель) через Add Object → POUs for Implicit Checks.
проект
— рантайм не проверяет «деление на ноль» — нет активной функции контроля этой категории
проект
— рантайм не проверяет «выход за диапазон» — нет активной функции контроля этой категории
проект
— рантайм не проверяет «разыменование указателя» — нет активной функции контроля этой категории
Мелочи 13 шт.
Мёртвый код — POU нигде не вызывается6 · dead-pou
POU нигде не вызывается и не используется как тип — мёртвый код либо забытая правка имени (сверьте с ТЗ, прежде чем удалять).
Варианты решения
Не нужен — удалите POU.
Забытая версия — сверьте имя вызова с ТЗ, возможно опечатка в имени.
CfcDiv
— CfcDiv — не вызывается и не используется как тип
DivZeroVar
— DivZeroVar — не вызывается и не используется как тип
Main
— Main — не вызывается и не используется как тип
ktr_errors
— ktr_errors — не вызывается и не используется как тип
reindex_cascad
— reindex_cascad — не вызывается и не используется как тип
work_nasos_CO
— work_nasos_CO — не вызывается и не используется как тип
Криптовое имя переменной4 · naming
Односимвольные имена (кроме счётчиков i/j/k/n) нечитаемы при сопровождении. I/O-теги и пины FB не трогаем.
Варианты решения
Дайте осмысленное имя по назначению (напр. iRetryCount вместо n).
CheckBounds
— x — односимвольное имя, дайте осмысленное
Main
— d — односимвольное имя, дайте осмысленное
Main
— x — односимвольное имя, дайте осмысленное
Main
— c — односимвольное имя, дайте осмысленное
Дублированный блок логики (copy-paste)2 · copy-paste
Структурно совпадающие блоки (даже с переименованными тегами) — источник ошибок: фикс в одном месте забывают в остальных, а copy-paste чужого контура тащит его гейтинг-флаги. Кандидат на общий FB с параметрами.
Варианты решения
Создайте один FUNCTION_BLOCK с входами-параметрами (уставки, теги контура) и инстанцируйте на каждый контур.
Для поканальных повторов — цикл FOR по массиву структур.
ktr_errors
— внутри ktr_errors: 1 дублированных блок(ов), повтор до 5× (~13 стр.) — вынести в цикл/FB
reindex_cascad
— внутри reindex_cascad: 1 дублированных блок(ов), повтор до 5× (~13 стр.) — вынести в цикл/FB
Magic number — сырая константа в коде1 · magic-number
Крупная/дробная сырая константа без имени — при правке уставки легко пропустить и трудно понять смысл. Вынесите в VAR CONSTANT. Индексы массивов, границы FOR и мелкие целые (<16) не считаются; одинаковые константы объединены по значению.
Варианты решения
Объявите именованную константу и используйте её: VAR CONSTANT SEC_PER_HOUR : UDINT := 3600; END_VAR
ktr_errors:18
— константа 50 — вынести в именованную VAR CONSTANT
gvl.nasos_1_freq := 50;
Это синтетический проект. На вашем находок обычно больше.
Экспорт из CODESYS занимает полминуты, отчёт — столько же. Файл обрабатывается в памяти и не сохраняется. Нет проекта под рукой — скачайте этот и прогоните его сами.
Чего линтер НЕ видит (нужен глаз и сверка с ТЗ): полярность сигналов, перепутанный гистерезис (< set+gist / > set−gist), copy-paste чужого контура. Отчёт — повод перепроверить, а не приговор.