Объектно-ориентированное программирование (ООП) – концепция, которая призвана облегчить разработку сложных систем, за счет введения новых понятий, более приближенных к реальному миру, чем функциональные и процедурные языки программирования. Как пишет википедия, «Обычный человеческий язык в целом отражает идеологию ООП, начиная с инкапсуляции представления о предмете в виде его имени и заканчивая полиморфизмом использования слова в переносном смысле, что в итоге развивает выражение представления через имя предмета до полноценного понятия – класса.»
Но с точки зрения всех, кто впервые сталкивался эти этим абстракциями, после классических процедурных языков понятнее не становилось, кажется наоборот все еще больше запутывалось.
С другой стороны, есть графические нотации работы программы, которые не приближены к человеческому языку, но гораздо понятнее, чем даже любой код, не то что ООП. Вполне возможно, что это более понятно мне, испорченному инженерным образованием, но таких как я много и этот текст для таких же испорченных физиков, не понимающих высокие абстракции.
Вот, например, реальное описание в графической нотации алгоритма управления задвижками АЭС.:
Рисунок 1. Пример программы управления АЭС в графической нотации
Слева входные сигналы, справа команды.
Мне кажется, что такой алгоритм прочитать может даже ребенок:
- Если насос включен в течении 60 секунд и расход меньше 10, то задвижку на рециркуляции открыть.
- Если насос включен, то подавать в течении 5 секунд на задвижки 001 и 002 команду открыть.
- Если расход больше 20 и насос включен, то в течении 5 секунд на задвижку 003 подавать команду закрыть.
В бытность мою студентом я подрабатывал, создавая библиотеку компонентов для Delphi и был знаком с ООП не понаслышке. Потом, когда столкнулся с реальными программами управления АЭС, очень удивился что нет никакого абстрагирования, инкапсуляции и, прости господи полиморфизма, только чистый Си, и еще желательно урезанный правилами и рекомендация MISRA C, чтобы все было надёжно, переносимо, безопасно.
Вершиной обрезания Си в моей практике был язык FIL, для систем управления реакторами РБМК. В нем функции заранее писались на Си, компилировались, а потом вызывались на основе текстового файла, где они были описаны на языке FIL. В итоге, можно было вызвать только ограниченный, но тщательно проверенный и отлаженный набор функций. И все это – во имя безопасности и надежности.
Но при этом система управления реактором и в целом система управления АЭС – это как раз тот случай, где принципы ООП должны применятся в полный рост. В само деле, есть множество однотипного оборудования – задвижки, насосы, датчики, всё легко классифицируется, есть готовые объекты, соответствующие реальному оборудованию. Казалось бы, вот оно – применяй ООП, классы, наследование, абстрагирование и полиморфизм. Но нет, нужен чистый Си и это требования безопасности.
А дальше – еще интереснее. На самом деле программу для управления АЭС пишет не программист, а технолог – только он знает, что и когда закрывать, открывать, включать, а главное – он знает, когда всю это байду выключать, чтобы не долбануло. А программист должен аккуратно всё это реализовать на коде Си. А еще лучше, что бы программиста вообще бы не было, а технолог сам рисовал технологические алгоритмы управляющих программ в графическом виде, автоматически генерировал код Си и загружал его в аппаратуру управления. Это рекомендуют международные стандарты по безопасности, в этом случае программист – как скрипач – не нужен. Он вносит только дополнительные ошибки и искажения в реализацию мыслей технолога.
Каково же было мое изумление, когда я узнал, что технологи и проектанты АЭС, сами независимо от программистов разработали и успешно применяют объектно-ориентированное программирование, да еще в графических нотациях, но при этом результирующий код полностью удовлетворяет требованиям безопасности и не содержит артефактов методологии ООП.
В самом деле, если посмотреть на код, который сгенерирован из схемы на рисунке 1 мы увидим чистый Си без всяких там классов.
Например таблица входа в алгоритм:
/* Index=0
UID=0
GeneratorClassName=TSignalReader
Name=KBA__AA.KBA31EY001.alg_inp
Type=Вход алгоритма */
state_vars->kbaalgsv0_out_1_ = kba31ap001_xb01;
state_vars->kbaalgsv0_out_4_ = kba31cf001_xq01;
Просто присвоение переменных.
Любой блок описывается как вычисление выхода по входу, с учетом параметров, заданных в списке констант. Например блок «Больше» выглядит в коде так:
/* Index=5
UID=5
GeneratorClassName=TLogBlock
Name=KBA__AA.KBA31EY001.smu.GT2
Type=Операция БОЛЬШЕ */
locals->v5_out_0_ = state_vars->kbaalgsv0_out_4_ > consts->kbaalgsv3_a_;
Выход блока это результат сравнение сигнала входа со значением в константе.
Таким образом, и в других блоках происходит последовательное вычисление локальных переменных из входных, и в конце цикла программы осуществляется запись в выходные переменные.
/* Index=14
UID=14
GeneratorClassName=TSignalWriter
Name=KBA__AA.KBA31EY001.alg_out
Type=Выход алгоритма */
if((action==f_InitState)||(action==f_GoodStep)||(action==f_RestoreOuts)){
kba31ey001_yb01 = locals->v8_out_0_;
kba31ey001_yb11 = state_vars->kbaalgsv9_out_0_;
kba31ey001_yb12 = state_vars->kbaalgsv12_out_0_;
kba31ey001_yb02 = locals->v13_out_0_;
};
А где здесь классы, спросите вы?
Вся методология, связанная с ООП, находится в именах переменных. Казалось бы, что такого может быть в имени переменной? А там может быть цела бездна. Например имя переменной kba31ap001_xb01, просто переменная в коде Си отвечающая требованием по наименованию переменных. Однако для технолога проектанта она выглядит примерно так: «Реакторное отделение, система промышленного водоснабжения, первый насос, пуск». Все это волшебство преобразования происходит благодаря замечательной немецкой системе кодирования (Kraftwerk-Kennzeichensystem) KKS, цитата:
“Данная система классификации кодирования предназначена для электростанций и обладает большими возможностями, а так же, учитывает особенности свободно-программируемых микропроцессорных технических средств.
Наряду с маркировкой технологического оборудования, исполнительных органов (запорно-регулирующей, предохранительной, отсечной и т.п. арматуры, механизмов собственных нужд), точек измерения, монтажных единиц, устройств автоматизации, зданий и сооружений, система KKS позволяет маркировать алгоритмы и программы различного вида и назначения (алгоритмы обработки измеряемых технологических параметров, сигнализации, автоматического регулирования, технологических защит, логического управления: блокировок, АВР, пошаговых программ, — расчета технико-экономических показателей и диагностики состояния технологического оборудования), входные, выходные и промежуточные сигналы этих алгоритмов и программ, видеограммы всех уровней, отображаемые на видеотерминалах, кабели и пр.».
Но самое интересное в последней части имени — _xb01, то что задается через знак подчеркивания. Если посмотреть на базу сигналов для проекта управления, то мы увидим там классы, понятные и знакомые всем, кто когда-то, как-то и где-то интересовался ООП (см. Рис. 2).
Рисунок 2. Пример структуры базы сигналов для системы управления АЭС.
У нас есть классы, или таблицы, на рисунке это столбец «Категории». Например, «KD1» у которых есть таблица шаблонных сигналов, полей класса Верхний предел измерения, нижний предел измерения, показание датчика и т.д. — это абстракция.
А так же есть реализация данного класса — конкретный датчик, например ТК21F02B1, расположенный в контуре, как вы уже догадались по его названию, в «Реакторном отделении, системе промышленного водоснабжения, у первого насоса», да и то, что это датчик расхода, тоже есть в этом названии, но это не точно.
И у этого экземпляра данного класса есть конкретные сигналы и их значения, в процессе работы программы, и к ним можно получить доступ по именам полей класса. Например, показание датчика рабочее обозначается переменной ТК21F02B1_XQ04.
На этом месте можно сказать, постой это же не совсем ООП, или даже совсем не ООП, тут же просто структуры данных, это есть и в стандартном Си. А где инкапсуляция методов в состав класса? Обработка данных должна быть в классе, тогда это и будет настоящий кошерный метод ООП.
Посмотрим, как выглядит в графическом виде подпрограмма контроля достоверности датчика. На рисунке 3 часть схемы обработки сигналов:
Рисунок 3. Пример программы обработки сигнала.
Видно, что в подпрограмме обработки используются имена переменных ТК21F02B1_XQ04, сформированные по правилам ККS и на основании таблицы полей класса. В приведенном примере происходит вычисление показания датчика в процентах ТК21F02B1_XQ03 по заданным значениям полей экземпляра класса таким, как ТК21F02B1_Xmin и ТК21F02B1_Xmax.
Если обратится к коду, сгенерированному из этой схемы, то мы увидим простое присвоение значение переменной, чистый Си и никаких плюсов и ООП.
/* Index=12
UID=12
GeneratorClassName=TSignalReader
Name=KD1.kd3_45.SR6
Type=Чтение из списка сигналов */
state_vars->su100v12_out_0_ = tk21f02b1_ai;
И присвоение результата расчета, тоже как простое присвоение переменной (с проверкой на действительность числа, что бы не уронить систему если в результате обработки сигналов мы получили ошибку)
/* Index=100
UID=100
GeneratorClassName=TSignalWriter
Name=KD1.kd3_45.SW3
Type=Запись в список сигналов */
if(isfinite(locals->v63_out_0_)){
tk21f02b1_xq04 = locals->v63_out_0_;
};
А в какой же момент появляется объединение данных полей класса методов обработки? На самом деле я знаком с двумя вариантами этого фокуса. Сейчас разберем один из них.
Посмотрим, как на схеме настраивается блок в котором расположена схема программы обработки (см. рис. 4).
У нас есть схема, на которую мы размещаем блоки субмодели графического языка программирования, внутри этих блоков находится графическая схема, часть которой приведена на рисунке 3, — программа обработки сигналов с датчиков.
В свойствах данного блока мы видим поля базы данных сигналов и выпадающий список, в котором находятся уже существующих в базе данных сигналов, экземпляры класса, конкретные датчики данного типа. Достаточно выбрать нужный датчик, экземпляр класса по имени и происходит чудо. В схеме все блоки чтение и записи получают имена типа ТК21F02B1_XQ03, (имя датчика экземпляра класса + имя поля).
Теперь при генерации кода Си все переменные получат значения нужного датчика. И программист не нужен, технолог все сделал сам когда разрабатывал схему в графическом языке програмирования для алгоритма управления АЭС.
Рисунок 4. Пример настройки схемы обработки датчика.
Для присвоения имен служит специальный скрипт автоматики в среде проектирования систем управления, примерно такой, как на рисунке 5. Всем блокам чтения на схеме присваиваются имена, состоящие из имени объекта и имени поля в классе (см. рис. 5).
Рисунок 5. Настройка имени переменных в блоках чтения.
Ясно, что аналогичным образом может быть создано неограниченное количество вариантов обработки сигналов, по сути методов для класса в методологии ООП. Точно так же могут быть сформированы для датчика, его подведение при отображении на видеокадрах SCADA системы, или например обработка процедур изменения уставок. Создается схема в графическом виде, сохраняется виде блока и используется при необходимости.
Подведу итог: в графических языках программирования методы ООП так же применяются и приносят пользу. А после генерации исходного кода управляющих программ, все артефакты методологии ООП, исчезают и остается, чистый С, безопасный, надежный, верифицируемый.
Понятно, что такое применение средств автоматизации кроме ускорения разработки, позволяет так же значительно сокращать время разработки количество ошибок в управляющих программах.
Автор: Вячеслав