
Общ преглед
Софтуерът за управление на кормилната система на индустриален камион беше актуализиран, за да отговаря на стандарта за безопасност ISO 13849. Екипът се сблъска със сериозни ограничения: липса на съществуващ процес, отговарящ на изискванията за безопасност, кратки срокове и разпръснат екип с ограничен опит в областта на безопасността. Изхождайки от стабилна изходна точка, те добавиха нов канал за безопасност, като същевременно поддържаха софтуера в състояние, позволяващо непрекъснато пускане на нови версии. Ясните изисквания, прецизната нотация, постепенната интеграция, дисциплинираното разклоняване и непрекъснатото тестване спомогнаха за поддържането на работния поток и избягването на дефекти в късен етап. Екипът достави софтуер, отговарящ на изискванията и с висока надеждност, в кратки срокове.
Цел
Беше необходимо да се усъвършенства системата за управление на индустриален камион, за да отговаря на изискванията на приложимия стандарт за функционална безопасност EN 1175, който се позовава на стандарта ISO 13849.
Софтуерът за контролера на двигателя и сензора на волана трябваше да бъде модифициран, за да отговаря на изискванията на тези стандарти, и да бъде изготвена необходимата документация за верификация, валидиране и безопасност.
Фактори, които трябва да се вземат предвид
Производителят на контролера на двигателя на кормилната система и на датчика на волана не е имал предишен опит в разработването на софтуер, отговарящ на стандартите за функционална безопасност, и следователно не е разполагал с процеси, които да могат да се използват за постигането на този резултат.
Въпреки че не бяха разработени с цел да отговарят на стандартите за функционална безопасност, продуктите – контролерът на двигателя и сензорът на волана – бяха разработени в продължение на няколко години, като се вземаше предвид безопасността, а софтуерът беше много стабилен. Липсваха обаче достатъчно данни от експлоатацията, които да подкрепят аргумента за безопасност, основан на „доказана в експлоатация“.
Клиентът имаше конкретни изисквания за безопасност, по-специално изискваше двуканална архитектура, но хардуерът на електрониката не беше с двупроцесорна архитектура. Използваният тип процесор обаче беше TMS 570, който е проектиран за приложения, свързани с безопасността, и разполага с двойни ядра в режим „lockstep“, както и с други функции за безопасност, като например ECC памет. Поради това, при внимателно обмисляне на проекта, беше възможно да се удовлетвори изискването за двуканална архитектура.
Само двама членове на екипа, който щеше да изпълнява работата, имаха предишен опит в разработването на софтуер, свързан с безопасността. Освен това екипът беше разпръснат в четири държави и на два континента.
Срокът за извършване на необходимите промени и за събиране на доказателствата за безопасност и съответствие със стандарта беше кратък.
Подход
Подходът се състоеше в това да се започне от съществуващата работеща и надеждна софтуерна версия, след което беше проектирана нова архитектура, включваща отделен канал за безопасност. Механизмите за безопасност бяха добавяни поетапно, като през цялото време функционалността на софтуера се поддържаше в напълно работно състояние.
При липсата на съществуващи процеси за разработка на софтуер в компанията се наложи да се създадат нови процеси, които да се спазват при работата. Това беше особено важно предвид липсата на предишен опит у много от членовете на екипа. Процесите осигуряваха както формално описание на начина, по който ще изпълняваме всеки аспект от работата, така и основа за обучение. Беше решено спецификацията на всички процеси, включително контролните списъци, да бъде включена в плана за безопасност на софтуера. Тъй като стандартът ISO 13849 не съдържа достатъчно подробни изисквания в областта на софтуера, като насока беше използван стандартът IEC 61508, част 3.
Беше създаден набор от жизнени цикли, около които да структурираме работата си. Целта на жизнените цикли беше да се внесе яснота относно това как различните дейности, които извършвахме, взаимодействаха помежду си. Те не се използваха, за да се наложи строга последователност на работата. Ако имаше смисъл да се започне и всичко беше на място, за да бъде успешно завършена, тогава дадена дейност можеше да започне. Планът за безопасност определяше необходимите ресурси за всеки вид дейност. Поддържахме план с балансирани ресурси, подробен за следващия месец и по-общ за следващите няколко месеца, който описваше действителната очаквана последователност и график на дейностите.
Тъй като времето за завършване на работата беше ограничено, се наложи да се установи начин на работа, който да гарантира създаването на софтуер с доказана безопасност в възможно най-кратки срокове. Трябваше да се избегнат типичните капани при разработката на софтуер: късно открити дефекти; повтарящи се преработки; проблеми с интеграцията; технически дълг; продължителна заключителна фаза на „отстраняване на последния бъг“. Нямаше време и за това първо да се разработи софтуерът, а след това да се документира. Софтуерът, неговият дизайн, верификацията му и доказателствата, необходими за потвърждаване на неговата безопасност, трябваше да бъдат създадени едновременно.
Скоростта не се постига чрез по-бързо изпълнение на отделните задачи. Тя се постига чрез елиминиране на факторите, които удължават инженерния процес. Целта беше максимална колективна ефективност, а не просто максимална скорост на изпълнение на отделните задачи. Вместо да се разглежда допълнителната строгост, необходима за безопасността, като допълнително усилие, тя беше използвана за намаляване на появата на дефекти и ускоряване на отстраняването им на всеки етап от жизнения цикъл, като по този начин се избягваше често срещаната продължителна фаза на отстраняване на грешки преди пускането на продукта. Акцентът беше поставен върху работата в екип, мотивацията и гарантирането, че екипът разполага с необходимата яснота, за да може да действа бързо. Същата документация спомогна както за осигуряване на яснота по време на разработката, така и за предоставяне на доказателства в подкрепа на твърденията за безопасност.
Процесът
Животният цикъл на най-високо ниво

Цикълът на разработка на най-високо ниво, който се използва, е показан на диаграмата по-горе. Той се основава на V-образния цикъл, както се препоръчва в стандартите за безопасност. Въпреки това той включва и итеративна разработка, и непрекъсната интеграция.
Анализ на изискванията за безопасност
В началото на проекта изискванията за безопасност на системно ниво бяха известни, но не бяха ясно дефинирани. Ето защо фазата на анализ на изискванията за безопасност се съсредоточи върху създаването на ясна и изчерпателна спецификация на изискванията за безопасност на софтуерно ниво. Първоначално се опитахме да направим това в Jira, но това се оказа твърде бавно. Затова се върнахме към варианта с изисквания в табличен формат в Word документ.
Изискванията бяха формулирани чрез математическа нотация, както и чрез други еднозначни нотации, като диаграми на състояния и таблици на състояния, където това беше възможно. Целта беше да се осигури максимална яснота за останалата част от разработката. Изискванията, изразени чрез тези нотации, се превръщат по-лесно в софтуер и позволяват да се избегнат проблемите, присъщи на естествения език, като неточност и двусмисленост.

Архитектурен проект
Въпреки че съществуващият софтуер беше структуриран съгласно документирана слоеста структура, тя не отговаряше на изискванията за софтуер, свързан с безопасността, поради което беше моделиран архитектурен проект с помощта на SysML (Visual Paradigm). За описанието на статичната структура бяха използвани блок-диаграми в SysML, а за описанието на динамичното поведение – диаграми на състояния и последователности. Някои примери са показани по-долу.
Архитектурният проект беше разработен на два етапа. Първият етап се състоеше в моделиране на съществуващата архитектура. След това бяха добавени допълнителни елементи за реализиране на функционалността за безопасност. Получената архитектура се състоеше от два ясно разграничени набора от модули – единият с основната функционалност, а другият – осигуряващ канал за проверка на безопасността.
Архитектурата в реално време представляваше детерминистичен цикъл на изпълнение, задействан от едно-единствено прекъсване. Веднъж след като даден модул започнеше да се изпълнява, той се изпълняваше докрай. Това означаваше, че въздействието на модулите, свързани с безопасността, върху модулите, несвързани с безопасността, беше ограничено. Бяха добавени изисквания за безопасност, за да се проверява за превишаване на времето за изпълнение, за презаписване на статични данни, както и за да се определи използването на отделни хардуерни периферни устройства за каналите, свързани с безопасността, и за тези, несвързани с безопасността.
Реализация на среда за тестване на модули
Тази дейност беше включена в жизнения цикъл, за да се предвиди възможността за необходимост от създаване на персонализирана тестова среда. Въпреки това беше взето решение да се използва готовият инструмент Tessy за тестване на модулите.
Планиране на тестовете за интеграция и квалификация
Планирането на тестовете се извършваше успоредно с разработването на изискванията и архитектурния проект. Тъй като проектирането на тестове осигурява полезна проверка на яснотата на изискванията и описанието на проекта, планирането на тестовете започна възможно най-рано. Важна цел на тази дейност беше тестовете, обхващащи дадена област от изискванията, да бъдат готови, преди софтуерните модули, реализиращи тези изисквания, да бъдат готови за тестове за интеграция. Целта беше да не се допусне натрупване на забавяне при завършените модули, които чакат да бъдат подложени на тестове за интеграция и квалификация.
Внедряване на среда за интеграционни и квалификационни тестове
Тестването на изискванията за безопасност на вградения софтуер често изисква наличието на специализирани инструменти и системи за тестване. Затова разработването им беше определено като изрична дейност в жизнения цикъл. Целта беше да се избегне ситуация, при която даден завършен модул е готов за интеграция, но средата за тестване не е готова да го тества.
Тест за интеграция и квалификация
остепенната интеграция и тестване бяха ключова част от нашия подход. Имахме късмета да започнем с много стабилна изходна точка. С завършването на всеки модул го добавяхме към нашата сборка и изпълнявахме всички приложими тестове за интеграция и квалификация. Струва си да се отбележи, че не автоматизирахме съставянето на сборки и единичните тестове. Бихме искали да го направим, но в някакъв момент, преди да започнем, някой беше развалил настройките на Jenkins и бяхме толкова притиснати от времето, че не дадохме приоритет на поправянето му. Всъщност не ни липсваше толкова много, което знам, че е ерес!
Важно е да се отбележи, че модулите не се включваха веднага в основната версия след приключването на тестването им. Инженерите първо изтегляха най-новата базова версия в своя клон, след което провеждаха интеграционни тестове, за да проверят дали кодът им действително работи както се очаква при интегриране и дали не причинява проблеми. В зависимост от естеството на модула към кода се добавяха инструменти за тестване, които позволяваха проверка на ключови вътрешни променливи и създаване на условия за грешки. Един от основните фактори за бързото пускане на версии беше да не се нарушава целостта на главния клон.
Софтуерът можеше да се конфигурира чрез данни, които се изтегляха отделно. Ние се възползвахме от това, за да улесним интегрирането на нови модули за безопасност, като добавихме флагове в нашите данни за активиране/деактивиране на функциите за безопасност (подобно на feature-flags). Това означаваше, че не се налагаше да чакаме всички модули, необходими за дадена функция за безопасност, да бъдат завършени, преди да започнем интегрирането. Въпреки това не използвахме тази възможност, за да интегрираме непълно проверени модули в основния клон.
Издаване на софтуер
Софтуерът се издаваше винаги, когато беше необходимо – било то за системни тестове, било то за тестване от страна на клиентите. Тъй като винаги разполагахме с напълно тествана версия, това се осъществяваше лесно. Всяко издание беше придружено от бележка към версията. Благодарение на строгото поддържане на система, при която всяка промяна в софтуера беше свързана със задача в Jira, а тези задачи бяха свързани с клонове в Git в съотношение 1 към 1, успявахме да изготвяме бележките към версията много бързо.



Животният цикъл на модула
Частта в сивото поле на жизнения цикъл на най-високо ниво представлява жизнения цикъл на модула. Той се прилагаше за всеки нов или променен модул. По този начин имаше множество екземпляри на този жизнен цикъл, изпълняващи се успоредно, като всеки от тях се отнасяше до разработката на нов или променен модул. Този жизнен цикъл е показан на диаграмата по-долу. Важно е да се отбележи тясната връзка, която поддържахме между нашите задачи в Jira и нашите клонове в Git. Всеки клон беше свързан с задача в Jira, което ни позволяваше лесно да проследим точно каква е причината за всяка промяна в кода ни. Бяхме много стриктни да не правим промени в клон, които не са пряко свързани със свързаната задача в Jira. Тази яснота се оказа безценна в по-късните етапи на проекта, когато все още добавяхме постепенно нови модули за безопасност, а също и правехме промени за отстраняване на открити бъгове. Написването на бележките към версията също беше много лесно.
Имайте предвид, че този жизнен цикъл, макар и да е изобразен като поредица от последователни дейности, не налага строга последователност. На практика тези дейности обикновено се изпълняваха от един инженер, който имаше свобода да организира работата си по свой избор.

Подробно проектиране
Подробното проектиране включваше подробна спецификация на изискванията към модула. Това осигури яснотата, от която инженерите се нуждаеха, за да разработят модула бързо и точно, както и за да го тестват ефективно чрез метода „черна кутия“.
Важно е да се отбележи, че подробният проект също така определяше точния интерфейс, предоставян от модула. Той също така определяше външните интерфейси на модула, които модулът трябваше да използва за конкретни цели, както беше посочено в архитектурния проект. Това осигуряваше яснотата, от която разработчиците на други модули, взаимозависими от този, се нуждаеха, за да работят бързо и точно.
Другите аспекти, обхванати от подробния проект, имаха за цел да гарантират, че реализацията на модула ще бъде лесна, чрез установяване на ясна структура на кода и разбиране на състоянията и обработката.
Внедряване и статичен анализ
Кодът беше написан на MISRA C. Всички правила на MISRA – както задължителните, така и препоръчителните – бяха приложени без глобални изключения. Всички случаи, в които правилата бяха деактивирани локално, бяха документирани с индивидуална обосновка под формата на коментар в кода. Всяко отклонение трябваше да бъде одобрено от рецензента на pull request, преди кодът да бъде обединен в нашия master клон.
Всички софтуерни модули, свързани с безопасността, бяха прегледани като цяло при първоначалното им написване. След това използвахме механизма за pull request, за да преглеждаме промените.
Module test planning
Софтуерът, свързан с безопасността, трябва да бъде подложен на задълбочено тестване. Необходими са десетки тестови случаи, за да се тества изцяло всеки модул. Това е, може би, най-непопулярният аспект при писането на код с висока степен на надеждност. Беше използван инструментът Tessy, който помогна за намаляване на ръчната работа, например чрез автоматично генериране на тестови мокове и стабс. Той също така позволи тестването на модулите като цяло, като ги изпълняваше многократно и проверяваше както времето за изпълнение, така и функционалността. Това намали броя на елементите, които се нуждаеха от тестване, и, което е по-важно, означаваше, че на ниво тестване на модулите можеше да бъде открит клас грешки, които иначе не биха били открити до етапа на интеграцията.
Използваните критерии за тестване включваха пълно покритие по метода „черна кутия“ на изискванията, посочени в подробната проектна спецификация, както и на всички класове на еквивалентност и всички гранични стойности. Изискваше се 100% покритие на редовете, но тестовете бяха разработени предимно въз основа на изискванията, а проверката на покритието представляваше вторична валидация, че в кода не е включен код, който не произтича от някое изискване.
Модулно тестване
Модулните тестове за даден модул се изпълняваха от инженера, написал кода (а в повечето случаи и подробния проект и спецификацията за тестване). Резултатите от модулното тестване се проверяваха като част от проверката на pull request-а. Рецензентите на pull request-а също така проверяваха дали могат да компилират и да изпълнят тестовете.
Резултати
Целта за създаване на софтуер с висока степен на интегритет в изключително кратки срокове беше постигната с много ограничено използване на специални техники или инструменти.
Приложеният процес беше подходящ за софтуер със средно ниво на безопасност и интегритет. За по-високи нива на интегритет подходът би могъл лесно да бъде разширен, като се включи използването на по-стриктни инструменти за специфициране и верификация, като например SPARK Ada.
Някои от факторите за постигане на успех бяха:
- Поддържане на напълно функционален продукт през целия процес и постепенното му усъвършенстване, без в нито един момент да се прави компромис с качеството или безопасността му
- Гарантиране, че дейностите не се започват, преди да са налице всички необходими ресурси за успешното им завършване
- Гъвкавост при подреждането и планирането на дейностите, при наличието на ясен жизнен цикъл, осигуряващ яснота относно взаимозависимостите между дейностите
- Разработване и проверка на всеки работен продукт, като се има предвид, че той е от решаващо значение за успеха на други дейности, за които служи като входни данни
- Наличие на екип от добри инженери и предоставяне на възможност да работят по най-добрия си начин: без да ги пришпорваме; като им позволяваме да се концентрират; и като им предоставяме автономност
- Непрекъснато планиране с балансирано разпределение на ресурсите, за да се гарантира, че инженерите не са претоварени, и незабавна реакция при променящи се обстоятелства
Важен беше внимателният и поетапен подход към разработването на продукта, при който на всеки етап се поддържаше напълно функционална версия, тъй като това гарантираше, че няма да се натрупат нерешени проблеми, които да се налага да се отстраняват в късните етапи на проекта. Трябва да се отбележи, че това не се постигаше чрез външно „управление“ или „контрол на етапите“, а зависеше изцяло от самите инженери, които поемаха отговорност както за целостта на разработваните от тях модули, така и за тяхната интеграция в продукта.
Избягваше се прибързаното започване на дейности, като се обръщаше внимание всички необходими входни данни да са достатъчно пълни, за да може дейността да бъде успешно завършена. По този начин не се създаваше натрупване на частично завършени дейности, които бяха блокирани в очакване на други задачи.
Въпреки че имаше ясно определен жизнен цикъл, се запази гъвкавост по отношение на това кои дейности да се изпълняват и кога. Това помогна екипът да остане зает, като се ускоряваше разработката на модулите, когато беше възможно да се започне и имаше свободен инженер. Например, имаше редица модули, които не се променяха, но се нуждаеха от подходящ подробен проект и единични тестове в съответствие с изискванията на стандарта за безопасност. Работата по тях можеше да започне, без да се чака завършването на изискванията и архитектурния проект.
За всеки работен продукт ключовият въпрос беше: „Как този работен продукт може да донесе максимална полза на инженерите, които извършват дейности, използвайки го като входни данни?“. Това беше важно за създаването на работни продукти, които не се произвеждаха просто защото някой стандарт или жизнен цикъл изискваше наличието им, а защото наистина добавяха стойност, като улесняваха или правеха по-сигурна работата по други дейности.
Може би е клише да се каже: „Най-важното беше екипът“, но въпреки това е факт, че нищо от това не би могло да бъде постигнато без инженери, които работят добре както индивидуално, така и като екип. Важно беше също така да се даде възможност на инженерите да покажат най-доброто от себе си. Това е твърде обширна тема, за да бъде разгледана тук, но трите ключови неща са: да не се оказва прекалено голям натиск по отношение на сроковете, който кара инженерите да бързат; да се даде възможност на инженерите да имат дълги периоди на интензивна концентрация, когато работят по сложни технически задачи; и да се предостави на инженерите колкото се може повече автономност.
Непрекъснатото планиране с балансирано разпределение на ресурсите е от съществено значение за почти всеки проект за разработка на софтуер. Изключително важно е да се гарантира, че нито един инженер не е претоварен и че времето на нито един инженер не се губи напразно. Всички дейности изискват участието на няколко инженери, така че планирането трябва да отчита този факт. Има и спомагателни дейности, които не трябва да се пренебрегват. С наличието на непрекъснато актуализиран план с балансирано разпределение на ресурсите беше възможно бързо да се адаптираме към обстоятелствата, като знаехме как това ще се отрази на текущите и планираните дейности.
