During the presentation, speaker told about common problems faced by software developers, provided examples of engineering errors and their costs, suggested ways of avoiding errors in the development, and covered the following topics:
- architecture and Impact Analysis;
- why developers write tests;
- cost of testing and ways to reduce it.
This presentation by Alexandr Ivanov (Lead Software Engineer, Consultant, GlobalLogic, Kharkiv) was delivered at GlobalLogic Kharkiv Embedded TechTalk #3 on November 16, 2018.
О чем хотелось бы поговорить сегодня.
Хотелось бы обратить Ваше внимание на ту часть программного обеспечения которую никто не видит. А именно на внутреннюю организацию или как мы часто говорим - архитектуру.
Архитектура может быть удачной или не очень удачной, а может и вовсе отсутствовать.
Сегодня я постараюсь провести некие аналогии с механикой, чтобы показать почему архитектуре все таки лучше быть.
Ну и для того чтобы не оставлять вопрос открытым, я попробую показать несколько шаблонов, применяя которые Вы сможете немного улучшить внутренюю организацию Вашего ПО.
К сожалению, очень часто встраиваемое ПО строится по принципу облачных технологий :)
Я конечно же имею ввиду монолитный способ организации кода, когда нет четко выделенных модулей, все инклудят всех, “если нашел функцию - вызывай”.
Люди чуть поопытнее, разделяют приложение на слои, ну хотя бы в документации и в структуре проекта на диске. При этом никто нет способа помечать разработчику вызвать функцию БЛ из ХАЛ добавив соответствующий заголовочный файл.
В моем, возможно идеализированном, представлении архитектура встраиваемого ПО имеет примерно такой вид.
Есть нижний слой модулей (ХАЛ) которые реализуют взаимодействие с периферией, выполняя каждый свою функцию.
Есть более высокий слой (ДРВ), обеспечивающий координацию работы нижестоящих ХАЛ модулей.
Есть бизнес логика, находящаяся так высоко, что до аппаратных средств просто не дотянуться.
Напоминает ли что-то Вам эта диаграмма?
Давайте отойдем немного в сторону и посмотрим как дела обстоят в механике.
В механике есть несколько базовых компонентов или узлов. Вот один из них.
Прелесть этого рычага в том, что зная длины плеч мы легко можем вычислить силы (или массы грузов) которые уравновесили бы его.
Нет ничего сложного в построении целой системы рычагов подвешенных друг на друга.
Пользуясь школьными знаниями физики и математики все массы грузов можно рассчитать.
Но что будет если мы добавим еще одну связь?
Вычисления станут несколько сложнее.
А если таких связей много?
Дело не в том что теперь расчет стал невозможным - он просто усложнился значительно.
Вернемся к тому что мы умеем хорошо.
В программном обеспечении так же есть “узлы” как и в механике. И они также состоят из простейших элементов. У нас это юниты (software units).
Отсюда и берет свое название юнит тестирование.
Целью юнит тестирования есть проверка каждого юнита (простейшего элемента) в отдельности.
Не всего узла или модуля - а одного юнита.
Пользуясь такой аналогией мы легко можем превратить нашу механическую систему в систему взаимодействующих между собой компонентов программы.
Да… Масс в софте не бывает. Это конечно же периферийные модули наших микроконтроллеров.
И точно так же как и в механике, дополнительные связи усложняют анализ программного обеспечения.
При большом количестве дополнительных связей мы не можем идентифицировать список фич которые нужно протестировать после внесения изменений в один юнит. Так же мы сталкиваемся с целым рядом проблем при модификации кода для реализации нового функционала.
Я думаю Всем вам эти трудности знакомы.
Что еще мы можем взять из механики?
Почему я задаю Вам и себе этот вопрос?
Потому что, на мой взгляд, механика как отрасль - намного более зрелая, чем разработка ПО.
Давайте посмотрим.
Как мы уже сказали и в механике и в ПО есть простейшие элементы (детали машин, например, и юниты)
Вы можете себе представить ситуацию, когда при производстве сложных механизмов, например автомобилей, изготовленные детали сразу бы шли на сборку без проверок и контроля качества?
Вы бы ездили на таком авто?
А мы (программисты) так делаем сплошь и рядом. У нас есть средство контроля качества - юнит тестирования. Но пользуемся мы им очень редко, особенно для встраиваемых приложений.
Чем это плохо?
У моего отца одно время был автомобиль Москвич. И он просто вынужден был проводить достаточное количество своего свободного времени за его ремонтом.
Не потому что он это очень любил. А потому что контроль качества на производстве мог бы быть более строгим.
Операционные системы на Ваших компьютерах обновляются каждый день не потому что Вас хотят бесплатно удивить новыми ощущениями от их использования и подарить вам незабываемый опыт. А потому что...
На слайде моя субъективная оценка затрат на разработку ПО с одним и тем же функционалом при юнит тестировании перед интеграцией, после интеграции, и вообще без юнит тестирования.
Чтобы немного снизить затраты на переделку и поддержку я предлагаю Вам использовать следующие паттерны интеграции юнитов. Это будет полезно даже если вы не пишете юнит тесты.
Ну а если пишете - то Вы сами со временем придете к таким вариантам интеграции юнитов. По Другому просто не получится писать тесты.
Если продолжать аналогию с механикой - то мы переходим к рассмотрению возможных соединений деталей. Оно может быть сварным, резьбовым, шпоночным, шлицевым .... ну я думаю нет смысла продолжать.
Еще раз остановимся на детали или юните. В программировании на языке Си не стоит долго искать что же является юнитом - эта единица трансляции языка. То есть си-файл со включенными в него заголовочными файлами.
И для того чтобы у юнит был независимой деталью нашей конструкции этот заголовочный файл должен быть один.
Я немного путанно подвожу Вас к мысли, что юни имеет интерфейс - в хедере и реализацию в си-файле.
У кубика Лего тоже есть интерфейс в виде верхних восьми возвышений и реализация в виде тела кирпичика.
Чем не минимальная единица больших конструкций?
Если положить два кубика Лего один на один - получится нечто похожее на букву I
Так можно реализовать слои абстракции, может быть сетевую модель OSI
Добавим еще один кубик и соединим со сдвигом - получим букву А
Один подписчик - два сервиса
Отзеркалим соединение - будет буква Y
Два подписчика на один сервис.
И пару рекомендаций как сохранить свои интерфейсы в чистоте если Вам нужны одни и те же типы данных или макросы в двух независимых юнитах.
Эта история уже больше про организацию заголовочных файлов.