Книга «Чистая архитектура. Искусство разработки программного обеспечения»
«Идеальный программист» и «Чистый код» — легендарные бестселлеры Роберта Мартина — рассказывают, как достичь высот профессионализма. «Чистая архитектура» продолжает эту тему, но не предлагает несколько вариантов в стиле «решай сам», а объясняет, что именно следует делать, по какой причине и почему именно такое решение станет принципиально важным для вашего успеха.
Роберт Мартин дает прямые и лаконичные ответы на ключевые вопросы архитектуры и дизайна. «Чистую архитектуру» обязаны прочитать разработчики всех уровней, системные аналитики, архитекторы и каждый программист, который желает подняться по карьерной лестнице или хотя бы повлиять на людей, которые занимаются данной работой. Все архитектуры подчиняются одним и тем же правилам! Роберт Мартин (дядюшка Боб)
Тест на профпригодность
Почему так много программного обеспечения превращается в микропрограммы? Похоже, что основная причина заключается в стремлении получить действующий встраиваемый код и практически не уделяется внимания его структурированию для увеличения срока службы. Кент Бек описывает три шага в создании программного обеспечения (далее в кавычках приводятся слова Кента, а курсивом выделены мои комментарии):
- «Сначала заставьте его работать». Вы останетесь не у дел, если он не работает.
- «Затем перепишите его правильно». Реорганизуйте код, чтобы вы и другие смогли понимать и развивать его, когда потребуется что-то изменить или понять.
- «Затем заставьте его работать быстро». Реорганизуйте код, чтобы добиться «необходимой» производительности.
Большая часть встраиваемых систем, которые мне приходилось видеть, похоже, писалась с единственной мыслью в голове: «Заставьте его работать», — и иногда с навязчивой идеей: «Заставьте его работать быстро», — воплощаемой введением микрооптимизаций при каждом удобном случае. В своей книге The Mythical Man-Month Фред Брукс предлагает «планировать отказ от первой версии». Кент и Фред советуют практически одно и то же: узнайте, как это работает, и найдите лучшее решение.
Встраиваемое программное обеспечение ничем не отличается в отношении этих проблем. Многие невстраиваемые приложения доводятся только до стадии «работает», и мало что делается, чтобы код получился правильным и служил долго.
Получение работающего приложения — это то, что я называю тестом на профпригодность для программиста. Программист, разрабатывающий программное обеспечение, встраиваемое или нет, который заботится только о том, чтобы получить работающее приложение, наносит вред своим продуктам и работодателю. Программирование — это нечто большее, чем умение писать работающие приложения.
В качестве примера взгляните на следующие функции, находящиеся в одном файле маленькой встраиваемой системы, написанные в ходе прохождения теста на профпригодность:
ISR(TIMER1_vect) { ... }
ISR(INT2_vect) { ... }
void btn_Handler(void) { ... }
float calc_RPM(void) { ... }
static char Read_RawData(void) { ... }
void Do_Average(void) { ... }
void Get_Next_Measurement(void) { ... }
void Zero_Sensor_1(void) { ... }
void Zero_Sensor_2(void) { ... }
void Dev_Control(char Activation) { ... }
char Load_FLASH_Setup(void) { ... }
void Save_FLASH_Setup(void) { ... }
void Store_DataSet(void) { ... }
float bytes2float(char bytes[4]) { ... }
void Recall_DataSet(void) { ... }
void Sensor_init(void) { ... }
void uC_Sleep(void) { ... }
В таком порядке функции были объявлены в файле с исходным кодом. А теперь разделим их и сгруппируем по решаемым задачам:
функции, реализующие предметную логику:
• float calc_RPM (void) {… }
• void Do_Average (void) {… }
• void Get_Next_Measurement (void) {… }
• void Zero_Sensor_1(void) {… }
• void Zero_Sensor_2(void) {… }
функции, обслуживающие аппаратную платформу:
• ISR (TIMER1_vect) {… }*
• ISR (INT2_vect) {… }
• void uC_Sleep (void) {… }
функции, реагирующие на нажатия кнопок:
• void btn_Handler (void) {… }
• void Dev_Control (char Activation) {… }
функция, читающая данные из аппаратного аналогово-цифрового преобразователя:
• static char Read_RawData (void) {… }
функции, записывающие значения в долговременное хранилище:
• char Load_FLASH_Setup (void) {… }
• void Save_FLASH_Setup (void) {… }
• void Store_DataSet (void) {… }
• float bytes2float (char bytes[4]) {… }
• void Recall_DataSet (void) {… }
функция, которая не делает того, что подразумевает ее имя:
• void Sensor_init (void) {… }
Заглянув в другие файлы этого приложения, я нашел множество препятствий, мешающих пониманию кода. Также я обнаружил, что организация файлов подразумевает единственный способ тестирования этого кода — непосредственно внутри целевого устройства. Практически каждый бит этого кода знает, что относится к специализированной микропроцессорной архитектуре, используя «расширенные» конструкции языка C1, привязывающие код к конкретному набору инструментов и микропроцессору. У этого кода нет ни малейшего шанса служить долго, если будет решено перенести продукт на другую аппаратную платформу.
Приложение работает: инженер прошел тест на профпригодность. Но нельзя сказать, что оно имеет чистую встраиваемую архитектуру.
Привязка к оборудованию — узкое место
Существует масса особых проблем, с которыми приходится сталкиваться разработчикам встраиваемых систем и не знакомых разработчикам обычного программного обеспечения. Например, ограниченный объем памяти, ограничения по времени выполнения операций, ограниченные возможности ввода/вывода, нетрадиционные пользовательские интерфейсы, а также наличие датчиков и контактов, соединяющих с внешним миром. В большинстве случаев аппаратное обеспечение развивается параллельно с программным обеспечением и микропрограммами. У вас, как инженера, разрабатывающего код для такого рода систем, может не быть места для запуска кода. Но это еще не самое худшее — полученное оборудование может иметь собственные недостатки, что замедляет разработку программного обеспечения больше, чем обычно.
Да, встраиваемое программное обеспечение имеет свои особенности, и инженеры встраиваемых систем — особые люди. Но разработка встраиваемых систем не настолько особенная, чтобы принципы, описываемые в этой книге, нельзя было применить к встраиваемым системам.
Одна из особых проблем встраиваемых систем — тесная зависимость от оборудования. Когда встраиваемый код структурируется без применения принципов и приемов чистой архитектуры, часто приходится сталкиваться со сценарием, когда код можно протестировать только на целевом оборудовании. Если это оборудование — единственное место, где возможно тестирование, такая тесная связь начинает замедлять вас.
Чистая встраиваемая архитектура — архитектура, поддерживающая тестирование
Давайте посмотрим, как применяются некоторые архитектурные принципы к встраиваемому программному обеспечению и микропрограммам и как они помогают избавиться от тесной привязки к оборудованию.
Уровни
Деление на уровни можно произвести разными способами. Начнем с трехуровневой организации, изображенной на рис. 29.1. Внизу находится уровень оборудования. Как предупреждает Дуг, вследствие совершенствования технологий и согласно закону Мура оборудование будет изменяться. Одни компоненты устаревают, и на смену им приходят новые компоненты, потребляющие меньше электроэнергии, или имеющие более высокую производительность, или стоящие дешевле. Независимо от причин я, как инженер встраиваемых систем, не хотел бы делать больше, чем необходимо, когда неизбежное изменение оборудования наконец произойдет.
Раздел между оборудованием и остальной частью системы — объективная реальность, по крайней мере после определения оборудования (рис. 29.2). Именно здесь часто возникают проблемы при попытке пройти тест на профпригодность. Ничто не мешает знаниям об оборудовании инфицировать весь код. Если не проявить осторожность при структурировании кода и не ограничить просачивание сведений об одном модуле в другой, код будет трудно изменить. Я говорю не только о случае, когда изменяется оборудование, но также о ситуации, когда понадобится исправить ошибку или внести изменение по требованию пользователя.
Смешивание программного обеспечения и микропрограмм — это антишаблон. Код, демонстрирующий этот антишаблон, будет сопротивляться изменениям. Кроме того, изменения сопряжены с опасностями, часто ведущими к непредвиденным последствиям. Даже незначительные изменения требуют полного регрессионного тестирования системы. Если вы не создали тесты с внешним оборудованием, готовьтесь проводить тестирование вручную, а затем ожидать новых сообщений об обнаруженных ошибках.
Оборудование — это деталь
Линия между программным обеспечением и микропрограммами обычно видна не так четко, как линия, разделяющая код и оборудование (рис. 29.3).
Одна из задач разработчика встраиваемого программного обеспечения — укрепить эту линию. Границу между программным обеспечением и микропрограммой (рис. 29.4) называют слоем аппаратных абстракций (Hardware Abstraction Layer; HAL). Это не новая идея: она была реализована в персональных компьютерах еще в эпоху до Windows.
Слой HAL существует для программного обеспечения над ним, и его API должен приспосабливаться под потребности этого программного обеспечения. Например, микропрограмма может хранить байты и массивы байтов во флеш-памяти, а приложению требуется хранить и читать пары имя/значение с использованием некоторого механизма хранения. Программное обеспечение не должно заботиться о деталях хранения пар имя/значение во флеш-памяти, на вращающемся диске, в облаке или в основной памяти. Слой аппаратных абстракций (HAL) предоставляет услугу и не раскрывает программному обеспечению, как она работает. Реализация поддержки флеш-памяти — это деталь, которая должна быть скрыта от программного обеспечения.
Еще один пример: управление светодиодом привязано к биту GPIO. Микропрограмма может предоставлять доступ к битам GPIO, а слой HAL может иметь функцию Led_TurnOn (5). Это довольно низкоуровневый слой аппаратной абстракции. Давайте посмотрим, как повысить уровень абстракции с точки зрения программного обеспечения/продукта. Какую информацию сообщает светодиод? Допустим, что включение светодиода сообщает о низком заряде аккумулятора. На некотором уровне микропрограмма (или пакет поддержки платформы) может предоставлять функцию Led_TurnOn (5), а слой HAL — функцию Indicate_LowBattery (). Таким способом слой HAL выражает назначение услуги для приложения. Кроме того, уровни могут содержать подуровни. Это больше напоминает повторяющийся фрактальный узор, чем ограниченный набор предопределенных уровней. Назначение ввода/выводов GPIO — это детали, которые должны быть скрыты от программного обеспечения.
» Более подробно с книгой можно ознакомиться на сайте издательства
» Оглавление
» Отрывок
Для Хаброжителей скидка 20% по купону — Чистая архитектура