﻿# Разработчики встраиваемых систем не умеют программировать

Заслуженно распространена точка зрения, что типичный разработчик высокоуровневого прикладного ПО настолько свыкся с доступностью системных ресурсов и мягкостью требований реального времени, что ожидать от него оптимизации кода в угоду снижения ресурсоёмкости приложения можно лишь в крайних случаях, когда этого прямо требуют интересы бизнеса\.


> Мы опубликовали и перевели эту статью с разрешения правообладателя\\\. Автор статьи – Павел Кириенко \\\(\[pavel\\\.kirienko@zubax\\\.com\]\(mailto:pavel\.kirienko@zubax\.com\)\\\)\\\. \[Оригинал\]\(https://habr\.com/ru/post/555498/\) опубликован на сайте Habr\\\.

Это и логично, ведь в задачах прикладной автоматизации самым дорогим ресурсом остаётся ресурс человеческий\. Более того, снижение когнитивных затрат на возню с байтами оставляет внимание разработчика свободным для задач первоочередной важности, таких как обеспечение функциональной корректности программы\.

![0951_Embedded_system_developers_ru/image1.png](https://import.viva64.com/docx/blog/0951_Embedded_system_developers_ru/image1.png)

Редко, когда речь заходит об обратной проблеме, имеющей место в куда более узких кругах разработчиков встраиваемых систем, включая системы повышенной отказоустойчивости\. Есть основания полагать, что ранний опыт использования MCS51/AVR/PIC оказывается настолько психически травмирующим, что многие страдальцы затем продолжают считать байты на протяжении всей карьеры, [даже когда объективных причин для этого не осталось](http://wikireality.ru/wiki/%D0%97%D0%B4%D0%B5%D1%81%D1%8C_%D1%82%D0%B0%D0%BA_%D0%BF%D1%80%D0%B8%D0%BD%D1%8F%D1%82%D0%BE)\. Это, конечно, не относится к случаям, где жёсткие ценовые ограничения задают потолок ресурсов вычислительной платформы \(микроконтроллера\)\. Но это справедливо в случаях, где цена вычислительной платформы в серии незначительна по сравнению со стоимостью изделия в целом и стоимостью разработки и верификации его нетривиального ПО, как это бывает на транспорте и сложной промышленной автоматизации\. Именно о последней категории систем этот пост\.

Обычно здесь можно встретить упрёк: "_~~Ты чё пёс~~_ _А MISRA? А стандарты AUTOSAR? Ты, может, и руководства HIC\+\+ не читал? У нас тут серьёзный бизнес, а не эти ваши побрякушки\. Кран на голову упадёт, совсем мёртвый будешь_\." Тут нужно аккуратно осознать, что адекватное проектирование ПО и практики обеспечения функциональной корректности в ответственных системах не взаимоисключающи\. Если весь ваш софт проектируется по [V\-модели](https://ru.wikipedia.org/wiki/V-Model), то вы, наверное, в этой заметке узнаете мало нового хотя бы уже потому, что ваша методология содержит пункт под многозначительным названием проектирование архитектуры\. Остальных эмбедеров я призываю сесть и подумать над своим поведением\.

![0951_Embedded_system_developers_ru/image2.png](https://import.viva64.com/docx/blog/0951_Embedded_system_developers_ru/image2.png)

## Не укради

Что, в конечном итоге, говорят нам вышеупомянутые стандарты в кратком изложении? Примерно вот что:

* **Не своевольничай**\. Наличие каждой строчки кода должно быть однозначно обосновано низкоуровневыми техническими требованиями и проектной документацией\.
* **Пиши простой код**\. У простого меньше шансов сломаться или выстрелить тебе в ногу, чем у сложного\.
* **Делай свои намерения явными и избегай неявных предположений**\. Это касается проверки инвариантов, исключения платформно\-зависимых конструкций, исключения UB, unsafe и схожих граблей, заботливо разложенных языком программирования и средой исполнения\.
* **Не забывай об асимптотической сложности**\. Ответственные системы обычно являются системами реального времени\. Адептов C\+\+ призывают воздержаться от злоупотреблений RTTI и использования динамической памяти \(хотя последнее к реальному времени относят ошибочно, потому что [подобающим образом реализованные](https://habr.com/ru/post/486650/) _malloc\(\)_ и _free\(\)_ выполняются за постоянное время и даже с предсказуемой фрагментацией кучи\)\.
* **Не игнорируй ошибки**\. Если что\-то идёт не так, обрабатывай как следует, а не надейся на лучшее\.

Это всё применимо в тех случаях, где непредвиденный сбой обходится бизнесу дороже, чем инвестиции в качественный софт и адекватную платформу\. Про тестирование тут, кстати, говорится сравнительно мало, потому что это несколько обособленная дисциплина\.

Видите пункт _"игнорируй широко известные принципы проектирования сложных информационных систем"_? Я тоже нет\. Но подавленные воспоминания о том, как кросс\-компилятор под дохлые однокристалки унижает разработчика в его собственном доме, не дают человеку в полной мере осмыслить свои действия и их долгосрочные последствия\. В итоге мы получаем спагетти без архитектуры, которое невозможно поддерживать и тестировать, но при этом оно носит гордую метку MISRA\-совместимости как \(сомнительное\) свидетельство качества\.

Я имел несчастье ознакомиться с некоторым количеством встраиваемого ПО реального времени, к надёжности которого предъявляются повышенные требования, и в пугающем числе случаев я ощущал, как у меня шевелятся на голове волосы\. Меня, например, сегодня уже не удивляет старая байка об [ошибках в системе управления Тойоты Приус](https://en.wikipedia.org/wiki/2009%E2%80%932011_Toyota_vehicle_recalls), или байка чуть поновее про [Boeing 737MAX](https://en.wikipedia.org/wiki/Boeing_737_MAX_groundings) \(тот самый самолёт, который [_проектировали клоуны под руководством обезьян_](https://www.forbes.ru/biznes/390923-etot-samolet-proektirovali-klouny-pod-rukovodstvom-obezyan-chto-sotrudniki-boeing)\)\. В нашем новом дивном мире скоро каждая первая система станет программно\-определяемой, что \(безо всякой иронии\) здорово, потому что это открывает путь к решению сложных проблем затратой меньших ресурсов\. Но с повальной проблемой качества системоопределяющего ПО нужно что\-то делать\.

Если взять типичную встраиваемую систему достаточно высокой сложности, можно выделить примерно следующие сгустки логики:

* Класс\-бог, отвечающий за всё сущее\.
* Несколько драйверов периферии и коммуникационных протоколов, ко внутренним состояниям которых зачастую класс\-бог обращается непосредственно, минуя хилые абстракции\.
* Utils или helpers, без них никуда\.

Что в итоге: модульное тестирование невозможно, потому что нет модулей; толковая поддержка невозможна, потому что всё сложно; о гарантиях функциональной корректности трудно рассуждать всерьёз, нам бы для начала научиться выполнять вменяемую декомпозицию задачи, а не решать все проблемы в одном цикле сразу\. Это зачастую преподносится как неотвратимая данность, [нельзя по\-другому](https://en.wikipedia.org/wiki/Einstellung_effect), потому что это же не десктоп система с неограниченными ресурсами, _вы не понимаете, это другое_\.

## Инфоцыгане

Косвенным образом масла в огонь подливают некоторые поставщики программных инструментов для разработчиков встраиваемого ПО: Mbed, Arduino, и т\.п\. Их маркетинговые материалы вполне могут заставить начинающего специалиста поверить, что суть этой работы заключается в низкоуровневом управлении железом, потому что именно на этом аспекте диспропорционально фокусируются упомянутые поставщики ПО\. Вот у меня на соседнем рабочем столе открыт в CLion проект ПО для одной встраиваемой системы; проект собирается из чуть более чем ста тысяч строк кода\. Из этой сотни примерно три тысячи приходятся на драйверы периферии, остальное приходится на бизнес\-логику и всякий матан\. Моя скромная практика показывает, что за исключением простых устройств сложность целевой бизнес\-логики приложения несопоставима с той его частью, что непосредственно работает с железом\.

В один прекрасный день я обсуждал детали своего опенсорсного проекта \(тесно связанного с эмбедами\) с разработчиком из сторонней компании; назовём его Ильёй \(в целях деанонимизации имя не было изменено\)\. Илья работал над фичей для моего проекта, которая требовалась его работодателю\. Илья работал внимательно и неспешно в силу своей аккуратности\. Илья регулярно созванивался со мной, и мы обсуждали, как лучше сделать то да сё\. Илья однажды сообщил, что пришёл к нему его начальник, отверз уста и изрёк:

> Смотри, что я нашёл\! Есть крутая новая система, Mbed называется, значит, для эмбедеров\. Гляди, как можно быстро прототипы лепить\! Клац, клац, и мигалка готова\! Вот же, на видео\. А ты, Илья, свой алгоритм оптимизации CAN фильтров пилишь уже неделю, не дело это, давай переходить на Mbed\.

Я, разумеется, не хочу сказать, что права на существование сии продукты не имеют\. Конечно, имеют, и более того: они чрезвычайно полезны, когда [бизнес\-логика проста и сосредоточена вокруг интеграции нескольких компонентов](https://en.wikipedia.org/wiki/Programming_in_the_large_and_programming_in_the_small)\. За примерами ходить не надо, просто откройте любой их рекламный проспект — это то, для чего они созданы\. Но у меня подгорает, когда я слышу о менеджере, продвигающем эмбеддед\-фреймворк для проекта, где работа с периферией и её отладка едва ли займёт 1% внимания программиста\.

Весь этот горький катаклизм знаком многим, кто вхож в отрасль\. Но ещё хуже обстоят дела, когда разработчик низкоуровневого ПО без надлежащей подготовки берётся за проектирование распределённых систем\.

## Когда один бэкэндер лучше двух эмбедеров

Ранее я публиковал [большую обзорную статью о нашем открытом проекте UAVCAN](https://habr.com/ru/post/512570/) \(_Uncomplicated Application\-level Vehicular Computing And Networking_\), который позволяет строить распределённые вычислительные системы \(жёсткого\) реального времени в бортовых сетях поверх Ethernet, CAN FD или RS\-4xx\. Это фреймворк издатель\-подписчик примерно, как DDS или ROS, но с упором на предсказуемость, реальное время, верификацию, и с поддержкой baremetal сред\.

Для организации распределённого процесса UAVCAN предлагает предметно\-ориентированный язык — DSDL \(Data Structure Description Language\) — с помощью которого разработчик может указать типы данных в системе и базовые контракты, и вокруг этого затем соорудить бизнес\-логику\. Это работает примерно, как REST эндпоинты в вебе, XMLRPC, вот это вот всё\. Если взять одного обычного бэкендера — человека, измученного [сервис\-ориентированным проектированием](https://ru.wikipedia.org/wiki/%D0%A1%D0%B5%D1%80%D0%B2%D0%B8%D1%81-%D0%BE%D1%80%D0%B8%D0%B5%D0%BD%D1%82%D0%B8%D1%80%D0%BE%D0%B2%D0%B0%D0%BD%D0%BD%D0%B0%D1%8F_%D0%B0%D1%80%D1%85%D0%B8%D1%82%D0%B5%D0%BA%D1%82%D1%83%D1%80%D0%B0) и поддержкой сложных распределённых комплексов — и объяснить ему суть реального времени, то он в короткие сроки начнёт выдавать хорошие, годные интерфейсы на UAVCAN\.

Хрестоматийный пример — интеграция [системы воздушных сигналов](https://ru.wikipedia.org/wiki/%D0%A1%D0%B8%D1%81%D1%82%D0%B5%D0%BC%D0%B0_%D0%B2%D0%BE%D0%B7%D0%B4%D1%83%D1%88%D0%BD%D1%8B%D1%85_%D1%81%D0%B8%D0%B3%D0%BD%D0%B0%D0%BB%D0%BE%D0%B2), или хотя бы одного лишь датчика воздушной скорости\. Наученный тяжким опытом разработки и сопровождения продакшена, бэкендер тщательно осмыслит следующий вопрос: "_какую бизнес\-задачу мы решаем?_"

![0951_Embedded_system_developers_ru/image3.png](https://import.viva64.com/docx/blog/0951_Embedded_system_developers_ru/image3.png)

Допустим, ответ подопытного является вариацией на тему "_измерение воздушной скорости, барометрической высоты и статического давления_"\. Тогда на свет появляются примерно следующие строки DSDL:

```cpp
# Calibrated airspeed
uavcan.time.SynchronizedTimestamp.1.0 timestamp
uavcan.si.unit.velocity.Scalar.1.0    calibrated_airspeed
float16                               error_variance
```



```cpp
# Pressure altitude
uavcan.time.SynchronizedTimestamp.1.0 timestamp
uavcan.si.unit.length.Scalar.1.0      pressure_altitude
float16                               error_variance

# Static pressure & temperature
uavcan.time.SynchronizedTimestamp.1.0 timestamp
uavcan.si.unit.pressure.Scalar.1.0    static_pressure
uavcan.si.unit.temperature.Scalar.1.0 outside_air_temperature
float16[3] covariance_urt
# The upper-right triangle of the covariance matrix:
#   0 -- pascal^2
#   1 -- pascal*kelvin
#   2 -- kelvin^2
```

Мы получаем законченный сетевой сервис, который предоставляет данные _системы воздушных сигналов_ \(конечно, этот пример не претендует на звание законченного сервиса, но суть вы поняли\)\. Если потребитель хочет, например, знать барометрическую высоту, он просто берёт и подписывается на соответствующий топик\.

Знакомый с физикой полёта читатель здесь уместно поинтересуется: откуда оконечному устройству \(что поставляет сервис воздушных сигналов\) знать [калибровочные параметры датчика воздушной скорости](https://en.wikipedia.org/wiki/Calibrated_airspeed)? Ведь это предполагает осведомлённость датчика о собственном положении на летательном аппарате и об его аэродинамических свойствах\. Принципы инкапсуляции и разделения ответственности предписывают, что соответствующие параметры настраиваются на самом поставщике сервиса воздушных сигналов \(т\.е\. сетевом узле датчика\) с целью сокрытия деталей реализации сервиса от его потребителей\.

В БПЛА некоторых типов практикуется автокалибровка, где выборка измерений с приёмника воздушного давления за значительный промежуток времени сопоставляется с наземной скоростью, чтобы таким образом определить калибровочные параметры эмпирически\. В рамках сервис\-ориентированного подхода это решается превращением системы воздушных сигналов в потребителя данных о наземной скорости\. Проще говоря, наш сетевой узел, что измеряет воздушную скорость, подписывается на топик, в котором есть данные о наземной скорости летательного аппарата, получая таким образом доступ к необходимому контексту для выполнения автокалибровки\.

"Очевидные вещи говоришь" — скажет матёрый архитектор информационных систем — "Тут сервис, тут зависимость, соединили и полетели"\. Но в кругу встраиваемых систем эти очевидные вещи, судя по моему удручающему опыту, являются откровением, потому что опытный эмбедер подходит к решению той же проблемы совершенно иным образом: не от целей, стоящих перед системой, а от средств их достижения\. Или, короче говоря: первым вопросом является не _что_ мы делаем, а _как_ мы это делаем\. В итоге вместо сервиса мы получаем один\-единственный топик следующего типа:

```cpp
uint16 differential_pressure_reading
uint16 static_pressure_reading
uint16 outside_air_temperature_reading
```

Это, конечно, нельзя использовать непосредственно, поэтому наше конечное устройство превращается в пассивный сенсор, который сообщает измерения на центральный узел, и тот затем выполняет вычисления и публикует их результаты в сеть в узкоспециализированных форматах, по одному под конечного потребителя\. Например, если гироподвес и привод предкрылков нуждаются в воздушной скорости, то к каждому будет приделан отдельный топик своего типа\. Своими глазами видел\.

В итоге мы получаем всё то же **спагетти с классом\-богом**, только вместо класса у нас **центральный узел**, а вместо спагетти — **сотня топиков без архитектуры**\. Нетрудно заметить, что этот подход также может увеличить время доставки данных и загрузку сети, а заодно и, возможно, снизить отказоустойчивость ввиду централизации процессов\.

## Художника каждый может обидеть

Не следует думать, что я хочу определить всех эмбедеров в биореактор\. Я, в конце концов, и сам один из них\. Но я склонен к мысли, что легче из хорошего прикладного программиста сделать толкового эмбедера, чем дождаться вменяемого кода от последнего\.

_Коллеги, одумайтесь_\.

Я вижу, как нашим микроскопом заколачивают ржавые гвозди, и представляю, сколько ещё подобного происходит за пределами моего поля зрения\. В прошлом году уровень отчаяния в нашей скромной команде был столь высок, что мы опубликовали наноучебник, где объясняется, как выглядит сетевой сервис здорового человека: [UAVCAN Interface Design Guidelines](https://forum.uavcan.org/t/the-uavcan-guide/778)\. Это, конечно, капля в море, но в один прекрасный день я всё\-таки переведу его на русский язык ради подъёма уровня профессиональной грамотности\.

Непонимание основ организации распределённых вычислений затрудняет внедрение новых стандартов на замену устаревших подходов\. Наши наработки в рамках [стандарта DS\-015 \(созданного в коллаборации с небезызвестными NXP Semiconductors и Auterion AG\)](https://www.dronecode.org/introducing-ds-015-uavcan-drone-standard/) встречают определённое сопротивление ввиду своей непривычности для целевой аудитории, в то время как ключевые принципы, на которых они основаны, известны индустрии информационных технологий уже не одно десятилетие\. Этот разрыв должен быть устранён\.

Желающие принять участие в движении за архитектурную чистоту и здравый смысл могут причаститься в телеграм\-канале [uavcan\_ru](https://t.me/uavcan_ru) или на форуме [forum\.uavcan\.org](https://forum.uavcan.org/)\.