Шон Престридж – старший инженер по применению (FAE), руководитель группы FAE американского подразделения IAR Systems – в статье «Move fast and break things? Not so fast in embedded», рассказывает о специфике разработки программного обеспечения для встраиваемых систем, уделяя особое внимание вопросам качества кода и тестирования.
«Двигайся быстрее и ломай преграды» — это подход, озвученный Марком Цукербергом, который он внедряет в культуру разработки Facebook. Несмотря на то, что он чудесно звучит, когда мы говорим о быстром создании и запуске новых функций (даже если они не идеальны), все же он теряет свою красоту, если попытаться применить его к разработке программного обеспечения для встраиваемых систем.
Причина в том, что предметные области совершенно разные. В основе Facebook лежит разработка в области веб-технологий и баз данных с множеством функциональных точек, которые вероятно не слишком пострадают, если важная новая функция не будет работать корректно. Встраиваемые системы – по своей природе – это системы с ограниченными ресурсами, в основном предназначенные для выполнения только одной функции, ну или может быть нескольких функции. Поэтому философия «Двигайся быстрее и ломай преграды» применительно к встраиваемой системе может потенциально сделать всю систему бесполезной. В зависимости от того, какую функцию реализует встраиваемая система, результаты могут быть в лучшем случае неприятными, а в худшем – катастрофическими.
Означает ли это что нельзя применять методы быстрой разработки приложений (RAD – Rapid Application Development) для встраиваемых систем? Конечно нет, такие методы можно использовать, но нужно быть очень аккуратным в том, как это делать.
«Спешка не делает нас ни быстрее, ни продуктивнее»
Быстрая разработка подразумевает что качество кода уходит на второй план, обеспечивая тем самым быструю доставку кода. Иногда это называют синдромом WISCY (произносится как «виски»): почему еще никто не программирует? Это также означает что тестирование кода не делается вообще или делается формально. Во всяком случае, и то и другое может вызвать проблемы в проекте, поэтому следует придерживаться лучших практик, гарантирующих качество кода.
Когда разработчики спешат добавить новую функциональность (или исправить ошибки) они, как правило, пропускают любое интеграционное тестирование с остальной частью системы и проверяют только свой код лишь за рабочим столом, выполняя несколько специально созданных для этой цели тестов. Причина такого поведения кроется в культуре спешки, которая требует выпускать код как можно быстрее. Как говорит Леми Эргин: «Спешка не делает нас ни быстрее, ни продуктивнее. Она усиливает стресс и отвлекает внимание. Нам нужны креативность, эффективность и внимательность» [1]. Быстрое развертывание программного обеспечения начинается с качества кода.
Разработка ПО для встраиваемых систем требует масштабируемости
При создании кода, разработчики должны сосредоточиться на таких процессах разработки, которые позволяют сделать код масштабируемым и минимизировать сложность системы, при этом они всегда ищут способы снизить «технический долг». Сохраняя простоту системы, разработчикам легче добавлять функционал или масштабировать систему, при необходимости. Однако для правильной разработки ПО требуется определенное время.
Искушение в ситуации с методами RAD состоит в том, чтобы пойти на «быстрое и грязное» решение, которое решает текущую проблему, стоящую перед командой разработчиков, и именно поэтому был введен термин «технический долг». Не планируя расширяемого или масштабируемого решения, разработчики обрекают себя на постоянное переписывание приложения для последующих проектов. Кроме того, исправление ошибок и добавление новых возможностей становится кошмаром из-за высокой связности ПО. Связность, в данном случае, определяет такую зависимость модулей программного обеспечения друг от друга, при которой изменение любой строчки кода (или во встраиваемых системах даже времени выполнения кода) может привести к неработоспособности всей системы.
Планирование может показаться излишним если смотреть на срок окончание проекта, а это чревато тем, что Келси Андерсон называет «ковбойским программированием». В результате, ради целесообразности снижается качество ПО и со временем возрастает сложность системы за счёт высокой связности и увеличения «технического долга». В итоге разработка фактически замедляется, а проект становится более дорогим [2]. Как утверждает Леми Эргин: «Без качественной кодовой базы невозможно быть гибким» [1].
Прим. Перев.:
Ковбойское программирование — это разработка программного обеспечения, при которой программисты имеют автономию в процессе разработки, которая включает в себя контроль графика проекта, языков, алгоритмов, инструментов, фреймворков и стиля программирования.
Всё начинается с качества кода – ведь это так просто
Как можно улучшить качество кода, когда не хватает времени даже на то, чтобы хоть как-то его написать, для того чтобы уложиться в график? К счастью есть такие стандарты как MISRA, CWE, CERT, которые могут в этом помочь. Эти стандарты были рассмотрены в предыдущей статье. Главное, они способствуют безопасной и надёжной практике программирования, избегая как опасного программирования того или иного поведения функций, так и «дыр» в стандартах языков Си и С++.
Используя инструменты анализа кода, которые могут автоматически сканировать код на наличие тех или иных отклонений от приведенных стандартов, можно быстро находить и исправлять проблемы, пока разработчик проверяет свой код за рабочим столом. Другими словами, разработчик получает мгновенную обратную связь по своему коду, всё ещё находясь в процессе его написания. В результате чего, разработчик исправляет ошибки более чем на 50 процентов чаще, чем если бы такая обратная связь поступала позже с сервера сборки [3]. Следование приведенным стандартам резко повышает качество кода, что, в свою очередь, снижает «технический долг» решения. Соответственно меньше ошибок добавляется в код, а те ошибки, которые есть, могут быть найдены и исправлены гораздо быстрее. Кроме того, подобный структурированный подход в программировании упрощает добавление функционала, а значит это можно делать быстрее. Но это лишь половина дела, потому что даже с качественной кодовой базой, всё равно нужно правильно тестировать.
Важно сочетание качества кода и тестирования
В своей основополагающей работе «Совершенный код», Стив Макконнел рассуждает о связи между тестированием и качеством кода:
Тестирование не повышает качества ПО — оно указывает на качество программы, но не влияет на него. Стремление повысить качество ПО за счет увеличения объема тестирования подобно попытке снижения веса путем более частого взвешивания. То, что вы ели, прежде чем встать на весы, определяет, сколько вы будете весить, а использованные вами методики разработки ПО определяют, сколько ошибок вы обнаружите при тестировании. Если вы хотите снизить вес, нет смысла покупать новые весы — измените вместо этого свой рацион. Если вы хотите улучшить ПО, вы должны не тестировать больше, а программировать лучше.» [4]
Обычно тестирование выполняется вручную, но с новыми инструментами, которые доступны командам разработчиков, становится возможным автоматизировать этот процесс. Несмотря на то, что эффективность таких инструментов варьируется от производителя к производителю, они всё же могут делать удивительные вещи, такие как: непрерывная проверка на соответствие техническим требованиям (соответствует ли код техническим требованиям спецификации по настоящему или это всего лишь «позолота»), модульное тестирование (хорошо ли конкретный программный модуль выполняет свои функции), интеграционное тестирование (все ли модули хорошо взаимодействуют друг с другом) и многое другое. Привлекательность автоматизированного тестирования может быть чем-то вроде сирены для разработчиков: Министерство обороны США предупреждает, что команды разработчиков должны быть реалистичны в своих подходах к автоматизированному тестированию с точки зрения того, что такое тестирование действительно выполнимо и что его успешное прохождение говорит о готовности кода к выпуску [5].
Две самые распространённые проблемы в разработке ПО для встраиваемых систем связаны с условиями гонки (когда получаются разные результаты в зависимости от того, какое условие завершилось первым) и проблемами инициализации. Обе проблемы могут привести к ошибкам, которые очень редко проявляют себя. Кроме того, такие ошибки трудно воспроизвести и, следовательно, трудно исправить и проверить. Автоматизированное тестирование, как правило, плохо справляется с поиском подобного рода ошибок и зачастую требуется творческий подход для того, чтобы найти способы проявить эти проблемы. Один из классических способов – это одновременно нажать несколько кнопок на устройстве. Получаются ли разные результаты если нажимать кнопки одновременно (в пределах одного машинного цикла)? А что будет если нажать одну кнопку, а затем нажать другую до того, как завершится подпрограмма обработки прерывания, вызванная первым нажатием? Обычно это делается с помощью ручного тестирования и, хотя может потребоваться много попыток для того, чтобы получить правильные комбинации, люди тем не менее невероятно хороши в этом деле.
Одна из мантр обеспечения качества заключается в том, чтобы «тестировать раньше, тестировать чаще» для того, чтобы понять, насколько процессы разработки выдерживают тщательную проверку. Это позволяет предпринимать корректирующие меры на раннем этапе разработки, что может сэкономить немало времени и денег. По оценкам IBM, если стоимость исправления ошибки стоит 100$ на этапе сбора требований к проекту, то исправить такую ошибку во время обычного цикла тестирование-и-исправление будет стоит уже около 1500$, а на этапе производства – 10000$ [6]. Вы быстро понимаете, что хотите найти как можно больше ошибок и как можно раньше. Это привело к появлению подхода «Разработка через тестирование» (Test-Driven Development – TDD), при котором создаются тесты из требований технического задания и обычно это происходит одновременно с началом проектирования. Идея заключается в том, что написанный и улучшенный код должен успешно проходить эти тесты, основанные на требованиях. Поэтому получаем повторяющиеся, очень короткие циклы разработки: пишем код, тестируем, повторяем пока программный модуль не заработает; затем переходим к следующему модулю. При этом тестирование первого модуля и его интеграция с другими модулями не прекращается. Таким образом, замедляя разработку вначале, мы ускоряем её к концу проекта.
Двигайся быстро, уделяя особое внимание качеству кода
Если необходимо двигаться быстрее в разработке, то сперва нужно улучшить качество кода с помощью стандартов программирования. К счастью, есть инструменты, которые могут в этом помочь, так что можно быстро находить и устранять ошибки, которые являются результатом небрежных или склонных к ошибкам практик программирования. Кроме того, необходимо спроектировать код так, чтобы он был масштабируемым и имел слабую связность для того, чтобы его можно было легко изменять, поддерживать и расширять. Наконец нужно быть реалистом в отношении того, что автоматизированное тестирование может дать вашей организации и осознавать, что полностью автоматизировать этот процесс – невозможно. Выполнение всего сказанного требует много времени, но в долгосрочной перспективе это приносит свои дивиденды.
- https://www.infoq.com/articles/slow-down-go-faster/
- https://www.targetprocess.com/articles/speed-in-software-development/
- https://cacm.acm.org/magazines/2018/4/226371-lessons-from-building-static-analysis-tools-at-google/fulltext
- Steve McConnell, Code Complete, Second Edition (Microsoft Press, 2009), 501.
- https://www.afit.edu/stat/statcoe_files/0214simp%202%20AST%20IG%20for%20Managers%20and%20Practitioners.pdf
- https://crossbrowsertesting.com/blog/development/software-bug-cost/